Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core SignalR Clients object disposed exception when calling from a callback

I have the following hub implementation in a new and clean ASP.NET Core 2.1 Web Application. I just updated to the latest version of ASP.NET Core 2.1 and the latest version of Visual Studio 2017.

The class worked and when launched from the debugger, one client connects. I can see this with the debugger, and, I can see this in the client because I log the "userobject" I send after it connected. The client remains connected.

In a second step, I injected the IPbxConnection into the hub, which works as well (I can see a valid object with the debugger). The IPbxConnection implementation will call the OnUserUpdated handler after 5 seconds (I just do this with a timer callback now in the IPbxConnection implementation for testing). This always results in an object disposed exception thrown on the Clients object. How can I send a notification to all clients in this callback? It seems the Clients object does not keep it's state and is only valid during the message handlers... however, I want to push information to the client at all times.

public class PresenceHub : Hub
{
    //Members
    private IPbxConnection _connection; 

    /// <summary>
    /// Constructor, pbx connection must be provided by dependency injection
    /// </summary>        
    public PresenceHub(IPbxConnection connection)
    {
        _connection = connection;
        _connection.OnUserUpdated((e) =>
        {                
            Clients.All.SendAsync("UpdateUser", "updateuserobject");
        });
        _connection.Connect();
    }

    /// <summary>
    /// Called whenever a user is connected to the hub. We will send him all the user information
    /// </summary>
    public override async Task OnConnectedAsync()
    {            
        await base.OnConnectedAsync();
        await Clients.Caller.SendAsync("AddUser", "userobject");
    }
}
like image 797
Wim Van Houts Avatar asked Jun 05 '18 19:06

Wim Van Houts


People also ask

Does SignalR require sticky session?

SignalR requires that all HTTP requests for a specific connection be handled by the same server process. When SignalR is running on a server farm (multiple servers), "sticky sessions" must be used. "Sticky sessions" are also called session affinity by some load balancers.

How many clients can SignalR handle?

In the default mode, the app server creates five server connections with Azure SignalR Service. The app server uses the Azure SignalR Service SDK by default. In the following performance test results, server connections are increased to 15 (or more for broadcasting and sending a message to a big group).

Is SignalR obsolete?

SignalR is deprecated. May I know the latest package for the same.

What causes SignalR to disconnect?

A SignalR connection can end in any of the following ways: If the client calls the Stop method, a stop message is sent to the server, and both client and server end the SignalR connection immediately.


2 Answers

As described in this issue on Github https://github.com/aspnet/SignalR/issues/2424 Hubs are short lived by design and are disposed after every invocation.

The only way I found to access your Clients outside of the current request Scope on your Hub is by injecting the HubContext into a seperate Class.

In your case a Broadcast to all Clients would look something like this

public class HubEventEmitter
{
    private IHubContext<PresenceHub> _hubContext;

    public HubEventEmitter(IPbxConnection connection, IHubContext<PresenceHub> hubContext)
    {
        _hubContext = hubContext;
        _connection.OnUserUpdated((e) =>
        {
            _hubContext.Clients.All.SendAsync("UpdateUser", "updateuserobject");
        });
    }
}

if you wanted to notify only specific Clients you'll have to collect the connectionId from the Context and use it like this

_hubContext.Clients.Client(connectionId).SendAsync("UpdateUser", "updateuserobject");
like image 147
Slizop Avatar answered Sep 21 '22 11:09

Slizop


This simple solution works for me. I haven't added anything extra to the start up class.

EDIT After some thought I decided that although this code works, it isn't a good pattern, not least because the static code ends up trying to use fields within a disposed object. A hub is a lightweight, short-lived container and should be used as such. I am therefore moving my long running process from static elements of the Hub into an IHostedService pattern

My hub contains a long running async process defined in a static member. Because Hubs are transient, on some occasions the hub is disposed when the async process tries to send messages. I have added a hub context for Injection into the Hub Constructor

public class IisLogFileHub : Hub
{
    IHubContext<IisLogFileHub> _hubContext = null;

    public IisLogFileHub(IHubContext<IisLogFileHub> hubContext)
    {
        _hubContext = hubContext;
    }
}

At any point in the long-running process, messages can be sent as follows

await _hubContext.Clients.All.SendAsync("ReceiveMessage", msg);
like image 25
pixelda Avatar answered Sep 20 '22 11:09

pixelda