.Net Core SignalR - connection timeout - heartbeat timer - connection state change handling

signalr reconnect
signalr ondisconnected
signalr keepaliveinterval
signalr persistent connection
asp.net core signalr
signalr check if user is connected
signalr inactivity timeout
signalr simulate disconnect

just to be clear up-front, this questions is about .Net Core SignalR, not the previous version.

The new SignalR has an issue with WebSockets behind IIS (I can't get them to work on Chrome/Win7/IIS express). So instead I'm using Server Sent Events (SSE). However, the problem is that those time out after about 2 minutes, the connection state goes from 2 to 3. Automatic reconnect has been removed (apparently it wasn't working really well anyway in previous versions).

I'd like to implement a heartbeat timer now to stop clients from timing out, a tick every 30 seconds may well do the job.

Update 10 November

I have now managed to implement the server side Heartbeat, essentially taken from Ricardo Peres' https://weblogs.asp.net/ricardoperes/signalr-in-asp-net-core

  • in startup.cs, add to public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
    app.UseSignalR(routes =>  
    {  
        routes.MapHub<TheHubClass>("signalr");  
    });

    TimerCallback SignalRHeartBeat = async (x) => {   
    await serviceProvider.GetService<IHubContext<TheHubClass>>().Clients.All.InvokeAsync("Heartbeat", DateTime.Now); };
    var timer = new Timer(SignalRHeartBeat).Change(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(30));            
  • HubClass

For the HubClass, I have added public async Task HeartBeat(DateTime now) => await Clients.All.InvokeAsync("Heartbeat", now);

Obviously, both the timer, the data being sent (I'm just sending a DateTime) and the client method name can be different.

Update .Net Core 2.1+

See the comment below; the timer callback should no longer be used. I've now implemented an IHostedService (or rather the abstract BackgroundService) to do that:

public class HeartBeat : BackgroundService
{
    private readonly IHubContext<SignalRHub> _hubContext;
    public HeartBeat(IHubContext<SignalRHub> hubContext)
    {
        _hubContext = hubContext;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _hubContext.Clients.All.SendAsync("Heartbeat", DateTime.Now, stoppingToken);
            await Task.Delay(30000, stoppingToken);
        }            
    }
}

In your startup class, wire it in after services.AddSignalR();:

services.AddHostedService<HeartBeat>();
  • Client
    var connection = new signalR.HubConnection("/signalr", { transport: signalR.TransportType.ServerSentEvents });
    connection.on("Heartbeat", serverTime => { console.log(serverTime); });

Remaining pieces of the initial question

What is left is how to properly reconnect the client, e.g. after IO was suspended (the browser's computer went to sleep, lost connection, changed Wifis or whatever)

I have implemented a client side Heartbeat that is working properly, at least until the connection breaks:

  • Hub Class: public async Task HeartBeatTock() => await Task.CompletedTask;
  • Client:

    var heartBeatTockTimer; function sendHeartBeatTock() { connection.invoke("HeartBeatTock"); } connection.start().then(args => { heartBeatTockTimer = setInterval(sendHeartBeatTock, 10000); });

After the browser suspends IO for example, the invoke method would throw an exception - which cannot be caught by a simple try/catch because it is async. What I tried to do for my HeartBeatTock was something like (pseudo-code):

function sendHeartBeatTock
    try connection.invoke("HeartbeatTock)
    catch exception
         try connection.stop()
         catch exception (and ignore it)
         finally
              connection = new HubConnection().start()
    repeat try connection.invoke("HeartbeatTock")
    catch exception
        log("restart did not work")
        clearInterval(heartBeatTockTimer)
        informUserToRefreshBrowser()

Now, this does not work for a few reasons. invoke throws the exception after the code block executes due to being run asynchronous. It looks as though it exposes a .catch() method, but I'm not sure how to implement my thoughts there properly. The other reason is that starting a new connection would require me to re-implement all server calls like "connection.on("send"...) - which appears silly.

Any hints as to how to properly implement a reconnecting client would be much appreciated.

Understanding and Handling Connection Lifetime Events in SignalR , NET Core SignalR. This article provides an overview of the SignalR connection, reconnection, ConnectionTimeout; DisconnectTimeout; KeepAlive; How to change timeout and keepalive settings connection and the transport connection begin and end at about the same time. English (United States). In this scenario, no intervention from the user application is involved, and when the SignalR client code establishes a new transport connection, it does not start a new SignalR connection. The continuity of the SignalR connection is reflected in the fact that the connection ID, which is created when you call the Start method, does not change.

.Net Core SignalR - connection timeout, .Net Core SignalR - connection timeout - heartbeat timer - connection state change handling. signalr ondisconnected signalr persistent connection signalr  It is hard to know what timeout properties we need to tweak (increase or decrease) to: Reduce the number of disconnects and reconnects. Not end up in situations 3 and 4 at all. An important thing to note here is that our .NET SignalR Client is actually a windows service which is connected to the server all the time.

Client version based on ExternalUse's answer...

import * as signalR from '@aspnet/signalr'
import _ from 'lodash'

var connection = null;
var sendHandlers = [];
var addListener = f => sendHandlers.push(f);

function registerSignalRConnection() {

    if (connection !== null) {
        connection.stop().catch(err => console.log(err));
    }

    connection = new signalR.HubConnectionBuilder()
        .withUrl('myHub')
        .build();

    connection.on("Heartbeat", serverTime =>
        console.log("Server heartbeat: " + serverTime));

    connection.on("Send", data =>
        _.each(sendHandlers, value => value(data)));

    connection.start()
        .catch(exception =>
            console.log("Error connecting", exception, connection));
}

registerSignalRConnection();

setInterval(() =>
    connection.invoke("HeartBeatTock")
        .then(() => console.log("Client heatbeat."))
        .catch(err => {    
            registerSignalRConnection();
        }), 10 * 1000);

export { addListener };

Net Core SignalR - connection timeout - heartbeat timer, Net Core SignalR - connection timeout - heartbeat timer - connection state change handling at AllInOneScript.com | Latest informal quiz  Stack Overflow for Teams is a private, secure spot for you and your coworkers to find and share information. Learn more How do implement a SignalR heartbeat/ping betweeen ASP.NET and Windows Server service?

SSE transport times out behind IIS due to idle connection · Issue , Net Core SignalR - connection timeout - heartbeat timer - connection state change handling. However, I don't manage to restart the connection  ASP.NET Core SignalR supports two protocols for encoding messages: JSON and MessagePack. Each protocol has serialization configuration options. JSON serialization can be configured on the server using the AddJsonProtocol extension method. AddJsonProtocol can be added after AddSignalR in Startup.ConfigureServices.

Terminate SignalR connections when the corresponding token , dotnet / aspnetcore One idea is to use KeepAlive heartbeat to check token expiry. cc @anurse Feel free to copy it, but if you adapt it for the JWT handler, double check Long polling connections currently timeout, though we should be Changing access token during SignalR session (with websockets  // before raising the Disconnected event to terminate the SignalR connection. GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(30); // For transports other than long polling, send a keepalive packet every

Build Real-time Applications with ASP.NET Core SignalR, SignalR also contained complex logic focused on managing the different protocols Hubs continue to be the main connection point between the server and its clients. NET Core SignalR applications with only minor code changes. associated with the connection event, the methods return a status to indicate if user has  Thank you for your time. Regarding the StopAsync. OK, I'll remove it and see how it works. Regarding the parameters: I am just trying to use the same parameters i did with the previous version of SignalR and I was pleased with the results.

Comments
  • Ah, one mystery solved. If I inject IServiceProvider into the Configure method, I can probably set this up. Vital piece of information...
  • So this works: app.UseSignalR(routes => { routes.MapHub<SignalRHub>("signalr"); }); TimerCallback SignalRHeartBeat = async (x) => { await serviceProvider.GetService<IHubContext<SignalRHub>>().Clients.All.InvokeAsync("Heartbeat", DateTime.Now); }; var timer = new Timer(SignalRHeartBeat).Change(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(30)); I shall update my question, now only the Client is left to take care of.
  • Thanks for posting your solution. Question about the server side code. I'm getting ObjectDisposedException when the timer fires. My ServiceProvider is dead by the time the timer fires. Did you have this problem and how did you solve it?
  • @EricVaughan stackoverflow.com/a/50641557/221683
  • I had to register my app's message handlers in an independent store otherwise they're lost when a new connection is created. When a message arrives, the connection's 'on' handler iterates over the collection, forwarding the message.
  • Thanks Pawel! The server timeout problem is solved, I've implemented the timer (see my comment above). The only remaining thing is a client re-connect after e.g. IO suspended or any other change of state of the connection. I can't figure out how to access that in the client library.
  • I have updated my question to show the implemented server-side, and my (failed) attempt to reconnect the client.
  • Could you post the ServerSide, too ?
  • @EricBrunner Have a look at the question, it shows the server part
  • @ExternalUse Also need the HeartbeatTock method on the server.
  • Also, currentRetryAttempt should be reset when a new connection has successfully been created.
  • Do you need heartbeats both from server side and client side? If so, why?