SignalR hub closes connection after method invoked - signalr

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.

Related

SignalR message from client not received

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.

Signal connecting to hub but can't invoke methods

The problem
We are experiencing some weird problems with our signalR service. The signalR service sometimes works, and sometimes it doesn't. The service connects without issues, but when we try to invoke some of the methods i get the following error:
An unexpected error occurred invoking XXXX on the server.
We have tried enabling detailed logging, but the error message was the same. In the snippet below you can see the error we are getting, and you can furthermore see that the client keeps pinging the server without problems after the error.
SignalR ws image
What we have tried
We have 3 different environments set up with signalR. One of the environments is working but we are experiencing the above mentioned error in our 2 other environments. We tried checking the code on the working environment down on one of those that didn't work, and that didn't fix our signalR problem. What we tried so far:
Restarting the signalR service.
Renaming the hubs.
Cleaning and rebuilding the solution.
The signalR logs doesn't tell us much too. This is the message we are getting out of the logs on Azure:
"message":"Connection aborted. Reason: Connection ping timeout.","type":"ConnectivityLogs","collection":"Connection"
This is the method that fails:
public async Task AddToItemGroupTest(Guid itemId)
{
var securityLogger = await CreateSecurityLoggerForConnectionChanges(itemId);
await Groups.AddToGroupAsync(Context.ConnectionId, GetItemGroupIdentifier(itemId, _applicationSettings.EnvironmentShortName));
securityLogger.LogDataCreate($"connection to itemhub on item with id: {itemId}");
}
Client side:
NotificationService.getItemConnection().invoke("AddToItemGroup" + Constants.environmentShortName, itemId)
.then(() => {
this.addGroupToActiveGroups("item", "AddToItemGroup" + Constants.environmentShortName, itemId);
connectionChangedCallback(connectionStatus.connected);
callback();
})
.catch((error) => {
connectionChangedCallback(connectionStatus.disconnected);
console.log(`Failed adding client to group. Error: ${error}`);
throw Error(`Failed adding client to group. Error: ${error}`);
});
getItemConnection looks like this:
private static getItemConnection() {
if (!this.itemConnection) {
this.itemConnection = new SignalR.HubConnectionBuilder()
.withUrl(Constants.itemHubUrl, {
accessTokenFactory: () => {
return getUserAccesTokenOrForceLogin(this.authService);
}})
.withAutomaticReconnect()
.build();
}
return this.itemConnection;
}

.NET Core SignalR, Server timeoute / Reconnect issue

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();

Detecting SignalR authorize failures from web client

I'm experimenting with SignalR hosted in an asp.net MVC5 application and am having an issue detecting authorization failures from a web client.
I have a simple hub as follows:
public class ChitChat : Hub
{
[Authorize]
public string Hi(string incoming)
{
Clients.All.echo(incoming);
return "Echoed " + incoming;
}
}
And on the web page:
$(function() {
var hub = $.connection.chitChat;
hub.client.echo = function(msg) {
console.log(msg);
}
$.connection.hub.start().done(function() {
console.log("Done starting hub");
hub.server.hi("Client message")
.done(function() {
console.log(arguments);
})
.fail(function() {
console.log(arguments);
});
})
.fail(function() {
console.log("Fail hub" + arguments);
});
});
When I enable detailed errors in the hub configuration, I get this in the promise rejection for hi
Error: Caller is not authorized to invoke the Hi method on ChitChat.
Without detailed errors, I just get
Error: There was an error invoking Hub method 'chitchat.Hi'.
I'd like to keep detailed errors off, but still get some dependable way of identifying auth failures (I'd assumed I would get a 401/403 code somewhere). Is there a way to achieve this?
SignalR uses jQuery under the hood, so detecting the 401 status code could be achieved by following:
$.ajaxSetup({
statusCode: {
401: function() {
// your code is here
}
}
});
Given a rejection r, check r.source.status. This should give you a 401/403 when it is an authorization problem.

SignalR external service timed loop - server or client

I have external services that I want to call every 20-30 seconds and make accessible to the client using SignalR but I'm not sure if I should have the client call the server in a JavaScript setInterval loop or if I should have the server run its own loop and push the information to the client. I'm looking to do dynamic page updates to show what a user is currently playing on Xbox 360/Xbox One or PlayStation Network.
Currently, I'm calling the server side code from the client in a setInterval loop, but I'm not sure how well this will scale. I had been doing Web API 2 calls through AJAX previously, but that will quickly eat up bandwidth. The external services are not mine so the only way to get simulated real-time results is to send a request to the external services periodically. The only problem I see with doing a server side loop is thread blocking. I don't want to stop the current thread while the loop is running.
Client side code:
var profile = $.connection.profileHub;
profile.client.receiveXboxProfile = function (profile) {
console.log(profile.GamerTag);
};
profile.client.receivePSNProfile = function (profile) {
console.log(profile.PSNId);
};
$.connection.hub.start()
.done(function () {
function GetGamerProfiles() {
profile.server.sendXboxProfile(window.UserName)
.done(function() {
})
.fail(function (error) {
console.log('SignalR ' + error);
});
profile.server.sendPSNProfile(window.UserName)
.done(function () {
})
.fail(function (error) {
console.log('SignalR ' + error);
});
}
GetGamerProfiles();
var id = setInterval(function () {
GetGamerProfiles();
}, 20000);
});
Server side hub:
[Authorize]
public async Task SendXboxProfile(string username)
{
// Makes call to external service to get Xbox profile
Clients.Caller.receiveXboxProfile(await xboxapi.GetProfile(username));
}
[Authorize]
public async Task SendPSNProfile(string username)
{
// Makes call to external service to get PSN profile
Clients.Caller.receivePSNProfile(await psnapi.GetProfile(username));
}
Maybe SignalR isn't the best solution for this problem since I don't control the external services, but I am very intrigued by the concept of SignalR and HTML 5 websockets. In general, is it better to do periodic updates on the client or server with SignalR?

Resources