I was initially trying to setup my server-side signalR HUB to send messages to the client, but so far have not succeeded.
So I decided to try to send a message from the client instead; and setup a button to trigger a message from client to server.
I can launch my Core 3.1 project (image below), and setup the Hub connection just fine, but cannot verify in any way that the message is being received on the server.
In fact, my server breakpoints never get hit.
In my html:
<button mat-button (click)="sendClientMessage()"> Send Message </button>
TypeScript component:
sendClientMessage(): void {
this.notificationService.sendMessageToHub();
}
import { Injectable } from '#angular/core';
import * as signalr from '#microsoft/signalr';
import { SIGCONT } from 'constants';
#Injectable({
providedIn: 'root',
})
export class NotificationService {
private hubConnection: signalr.HubConnection;
hubMessage: string;
public startConnection = () => {
this.hubConnection = new signalr.HubConnectionBuilder()
.withUrl('https://localhost:44311/hub')
.configureLogging(signalr.LogLevel.Debug)
.build();
this.hubConnection
.start()
.then(() => {
console.log('Hub Connection started');
this.sendMessageToHub();
})
.catch((err) => console.log(`Error while starting connection: ${err}`));
this.hubConnection.serverTimeoutInMilliseconds = 50000;
}
public hubListener = () => {
this.hubConnection.on('messageReceived', (message) => {
this.hubMessage = message;
console.log(message);
});
}
public sendMessageToHub = () => {
if (this.hubConnection == undefined || this.hubConnection.state === signalr.HubConnectionState.Disconnected) {
this.startConnection();
} else {
this.hubConnection.send('NewMessage', 'client', 'You have a notification from the front end !')
.then(() => console.log('Message sent from client.'));
}
}
constructor() { }
}
My server-side Core project - Notifications.cs
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NotificationHub.Hubs
{
public class Notifications: Hub
{
public async Task NewMessage(long username, string message)
{
await Clients.All.SendAsync("messageReceived", username, message);
}
internal Task NewMessage(string v1, string v2)
{
throw new NotImplementedException();
}
}
}
When I click the button above, it appears to send something to the server:
I would appreciate any help in getting my Core project to first hit those breakpoints, and to see what the NewMessage method is not receiving the client message.
From there I can try and figure out how to send messages from server to client (i.e. using some Timer example).
thank you.
You're sending 2 strings from the client to the server but you've made your server method take a long and a string so it doesn't match. If you looked at the server logs you would see a message about the method no being found.
Another way to observe the error would be to call invoke instead of send from the client side which will expect a response from the server on completion of the hub method, or in this case an error will be sent from the server.
Related
I have created an Azure SignalR (Serverless) reosurce in azure portal.
Then I have created an azure function HttpTrigger locally that references Microsoft.Azure.WebJobs.Extensions.SignalRService. In my azure function I have this code:
`public static class HttpTrigger
{
[FunctionName("Negotiate")]
public static SignalRConnectionInfo Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[SignalRConnectionInfo(HubName = "notificationHub")] SignalRConnectionInfo connectionInfo,
ILogger log)
{
log.LogInformation("Returning connection: " + connectionInfo.Url + "" + connectionInfo.AccessToken);
return connectionInfo;
}
[FunctionName("Notify")]
public static async Task<IActionResult> Notify([HttpTrigger(AuthorizationLevel.Function, "get", Route=null)] HttpRequest req,
[SignalR(HubName = "notificationHub")] IAsyncCollector<SignalRMessage> signalRMessage,
ILogger log)
{
log.LogInformation("Notify");
string msg = string.Format("Message from agent! {0} ", DateTime.Now);
await signalRMessage.AddAsync(
new SignalRMessage
{
Target = "notifications",
Arguments = new[] { msg }
});
return new OkObjectResult("ok");
}
}
`
Also in my azure function, this is what my local.settings.json looks like:
`
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"AzureSignalRConnectionString": "myconnstringhere"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "http://localhost:53377",
"CORSCredentials": true
}
}
To also solve the CORS problem, I have added http://localhost:53377 domain of my client part project.
My client part is a separate asp.net web application project . So here I am connecting to this azure function like this:
`
<script>
$(document).ready(function(){
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:7071/api/")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.onclose(start);
start(connection);
});
async function start(connection){
try {
await connection.start();
console.log("SignalR connected.");
connection.on("notifications", (message) => {
$("#detailcontainer").html(message);
console.log(message)
});
}
catch(err) {
console.log(err);
}
}
</script>
Now, I have published my azure function. But now it is not working anymore. It gets an error saying unauthorized when triggering /api/negotiate.
My azure function is a .net 6 project while the client app is a net framework 4.8. Is this because my client app is still in webforms?
I have added the connection string of my azure signalR to the application settings having a name format like this: Azure__SignalR__ConnectionString
I also have configured CORS allowed origins for my azure function, I added my client localhost app.
Closing this one because I found the answer. And it was really annoying that I have missed this one out.
I replaced AuthorizationLevel.Function to AuthorizationLevel.Anonymous. Because I am just passing the domain/api of my azure function and letting the signalR do its thing on their JS.
I using a net core 3.1 app with a very basic Hub. Everything works as expected locally. But on server the websocket connection is closed after first call is executed.
I am sure that websockets are enabled on the server. Another part using Blazor Server works fine. And also as long as no hub method is called the websocket connection is alive.
Also the client receives the data send by the hub.
Testing in chrome, after clicking the button, alert box shows the three messages. Then the connection is lost:
Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.
I already tested to connect to server with client from local machine, which does not work. So it shouldn't be a client issue here.
Working with local client and local running app, everything works fine.
TestHub.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace Test
{
public class TestHub : Hub
{
public async Task Test(string name)
{
await Clients.Caller.SendAsync("Message", $"Hello, World! ({ name }) 1");
await Clients.Caller.SendAsync("Message", $"Hello, World! ({ name }) 2");
await Clients.Caller.SendAsync("Message", $"Hello, World! ({ name }) 3");
}
}
}
Index.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.min.js"></script>
<h1>Test</h1>
<button onclick="test()">Test</button>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/Hub/Test")
.configureLogging(signalR.LogLevel.Information)
.build();
function start() {
try {
connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.log(err);
setTimeout(start, 5000);
}
};
connection.on("Message", (message) => {
alert(message);
});
function test() {
try {
connection.invoke("Test", "Foo");
} catch (err) {
alert(err);
}
}
start();
</script>
Referencing the nuget Mongo2Go caused the problems. With version 3.1.3 they added System.Text.Json 5.0.1 which breaks signalr.
Downgrading Mongo2Go to version 3.1.1 made signalr working again.
I have a SignalR hub written in my MVC solution, with a Javascript client connecting from the view.
The point of the connection is to receive changes for a wallboard from the server. This has to happen almost instantly and requires a lifetime connection, since the webpage is running on a screen without direct pc access.
So far the SignalR connection works for a couple of hours before it gives error.
The error I get is
Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message form the server.'.
Failed to load resource: net::ERR_CONNECTION_TIMED_OUT
Warning: Error from HTTP request. 0:
Error: Failed to complete negotiation with the server: Error
Error: Failed to start the connection: Error
Uncaught (in promise) Error
at new HttpError (singlar.js:1436)
at XMLHttpRequest.xhr.onerror (singalr.js:1583)
My client code
let connection = new signalR.HubConnectionBuilder()
.withUrl("/wbHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start().then(function () {
connection.invoke("GetAllWallboards").then(function (wallboard) {
for (var i = 0; i < wallboard.length; i++) {
displayWallboard(wallboard[i]);
}
startStreaming();
})
})
connection.onclose(function () {
connection.start().then(function () {
startStreaming();
})
})
function startStreaming() {
connection.stream("StreamWallboards").subscribe({
close: false,
next: displayWallboard
});
}
Hub Code:
public class WallboardHub : Hub
{
private readonly WallboardTicker _WallboardTicker;
public WallboardHub(WallboardTicker wallboardTicker)
{
_WallboardTicker = wallboardTicker;
}
public IEnumerable<Wallboard> GetAllWallboards()
{
return _WallboardTicker.GetAllWallboards();
}
public ChannelReader<Wallboard> StreamWallboards()
{
return _WallboardTicker.StreamWallboards().AsChannelReader(10);
}
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnDisconnectedAsync(exception);
}
}
Question 1: Is the way I handle reconnecting correct? From the error it feels like the .onclose works, but that it only tries one time? Is there anyway to try for x min before showing error?
Question 2: Reloading the website makes the connection work again, is there potential anyway to refresh the browser on signalR connection error?
I have the same issue (Question 1), and i resolve with this:
const connection = new SignalR.HubConnectionBuilder()
.withUrl("/hub")
.configureLogging(SignalR.LogLevel.Information)
.build();
connect(connection);
async function connect(conn){
conn.start().catch( e => {
sleep(5000);
console.log("Reconnecting Socket");
connect(conn);
}
)
}
connection.onclose(function (e) {
connect(connection);
});
async function sleep(msec) {
return new Promise(resolve => setTimeout(resolve, msec));
}
Every 5 seconds tries to reconnect, but i don't know if this is the right way to do this.
ASP.NET Core 2.1 (current LTS release) with the corresponding SignalR release doesn't seem to have some integrated reconnecting method avaliable. The code from #Shidarg doesn't work for me, it calls the reconnect method in a infinitive loop crashiny my browser. I also like the async/await syntax from C# more, so I updated it:
let reconnectWaitTime = 5000
let paramStr = '?myCustomArg=true'
let client = new signalR.HubConnectionBuilder()
.withUrl("/overviewHub" + paramStr)
.build();
client.onclose(async () => {
console.warn(`WS connection closed, try reconnecting with loop interval ${reconnectWaitTime}`)
tryReconnect(client)
})
await tryReconnect(client)
async function tryReconnect(client) {
try {
let started = await client.start()
console.log('WS client connected!')
// Here i'm initializing my services, e.g. fetch history of a chat when connection got established
return started;
} catch (e) {
await new Promise(resolve => setTimeout(resolve, reconnectWaitTime));
return await tryReconnect(client)
}
}
But for ASP.NET Core 3 they included a reconnecting method:
let client = new signalR.HubConnectionBuilder()
.withUrl("/myHub")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();
Per default it try three reconnects: First after 2 seconds, second after 10 seconds and the last about 30 seconds. This could be modificated by passing the intervalls as array parameter:
.withAutomaticReconnect([5000, 1500, 50000, null])
This example re-trys after 5s, 15s and 50s. The last null param tell SignalR to stop re-trying. More information could be found here: https://www.jerriepelser.com/blog/automatic-reconnects-signalr/
Configuring automatic reconnects only requires a call to withAutomaticReconnect on the HubConnectionBuilder. Here is what my JavaScript code looks like for configuring my connection:
connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();
You can configure the backoff period by passing an array of retry delays to the call to withAutomaticReconnect(). The default for this is [0, 2000, 10000, 30000, null]. The null value tells SignalR to stop trying. So, for example, if I wanted it to retry at 0, 1 second and 5 seconds, I can configure my HubConnectionBuilder as follows:
connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect([0, 1000, 5000, null])
.configureLogging(signalR.LogLevel.Information)
.build();
after merging angular app with asp.net MVC calling API from angular returns an empty JSON.
The angular and asp.net are in the same domain.
If I call the API With PostMan, I have a JSON with the result. but if I call it in the angular app my JSON result is empty.
Are there any tips for communicating angular app with asp.net MVC after merging and serving in the same domain?
Update 1:
The code that used to calling Webservice:
getSheets(): Observable<Sheet[]> {
return this.http.get(this.config.apiUrl + '/api/SheetsRelationAPI',
this.jwt())
.map(this.extractData)
.do(data => console.log('SheetsData:', data)) // debug
.catch(this.handleError);
}
/**
* Handle HTTP error
*/
private handleError(error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
const errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
// private helper methods
private jwt() {
// create authorization header with jwt token
const currentUser = JSON.parse(atob(this.cookie.getCookie('currentUser')));
if (currentUser && currentUser.access_token) {
const headers = new Headers({ 'Authorization': 'Bearer ' + currentUser.access_token},
);
return new RequestOptions({ headers: headers });
}
}
private extractData(res: Response) {
const body = res.json();
return body || [];
}
Update 2:
I notice that my API if I called it from outside domain it respond 2 times:
inspecting network with google chrome inspect element:
the first response is "zone.js" initiator and the second response is an "other" initiator
If I call the API from inside of the Domain I just have a response from "zone.js" initiator and it returns an empty JSON.
Update 3
export class OtherComponent implements OnInit {
sheets: Sheet[] = [];
errorMessage: string;
constructor(private httpService: HttpService) {
// this.sheets = this.ichartHttp.getSheets();
// console.log(this.sheets);
}
getSheets() {
this.httpService.getSheets()
.subscribe(
sheets => this.sheets = sheets,
error => this.errorMessage = <any>error
);
}
ngOnInit() {
this.getSheets();
}
}
The Problem is with my Authentication methods,
I use two types of authentication, MVC and WebAPI they conflict if I send a request to API under the same Domain.
So my Answer is: Your Angular Code looks good, take a look at your middleware project
I have the following types:
DataService - Gets data from the server using signalr hub.
AppComponent - which is the entry point for my main application
The data service constructor is as follows.
constructor(private http: Http) {
var hub = $.connection.spwHub;
hub.client.loadEmployees = this.loadEmployees;
$.connection.hub.start().done(() => {
...
});
}
My AppComponent is as follows:
constructor(service: DataService) {
this.company = service.getCompany();
service.getEmployees().then(employees => this.employees = employees);
this.departments = service.getDepartments();
}
I get the following error of course because the hub async call has not returned before the hub connection is made.
EXCEPTION: Error in ./AppComponent class AppComponent_Host - inline template:0:0 caused by: SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.
What is the best way to deal with this issue in AngularJs2?
You can use the APP_INITIALIZER hook to perform logic, get something prepped, whatever, that you need before the rest of the application runs.
In your app.module.ts (or whatever your main module is):
import { APP_INITIALIZER, NgModule } from "#angular/core";
export function init_app(employeeService: EmployeeService) {
return () => employeeService.getEmployees();
}
#NgModule({
<...>
providers: [EmployeeService, {
provide: APP_INITIALIZER,
useFactory: init_app,
deps: [ EmployeeService ],
multi: true
}]
})
export class AppModule { }
The service is returning a Promise which will be automatically handled:
getEmployees() {
return <...function stuff ...>
.toPromise();
}
And here's the github issue where this is documented (no doc on the angular.io site yet): https://github.com/angular/angular/issues/9047
After searching and finding nothing I gave the idea that components that don't need to be loaded should probably be deferred by default. This means that the answer is a no-brainer.
// start.component.ts
constructor() {
// Start the connection
var hub = $.connection.spwHub;
$.connection.hub.start().done(() => {
// This loads the next component and runs the constructor
this.initialized = true;
});
}
// start.component.html
<div *ngIf="initialized">
<main-component></main-component>
<div>
// This type is lazy loaded as soon as the initialized equals true.
// main.component.ts
constructor(employeeService: EmployeeService) {
// Finally, load the needed data.
this.employees = employeeService.getEmployees();
}