Reputation: 614
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");
}
}
Upvotes: 10
Views: 11339
Reputation:
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);
Upvotes: 9
Reputation: 7135
I was facing the same issue a while ago. I though too that the hub was the place to call when you c# backend needs to push a message, but actually this is not the case. The hub is more like a mounting point. Your c# backend should have its own client which connects to the hub as well. So your hub looks like this:
public class PresenceHub : Hub
{
public async Task Send(Userobject userobject)
{
await Clients.Others.SendAsync("AddUser", userobject);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
await Clients.Caller.SendAsync("AddUser", "userobject");
}
}
and than a custom class / piece of code which calls the hub like this
public class MyCustomCode{
public PresenceHub(IPbxConnection connection)
{
hubConnection = new HubConnectionBuilder().WithUrl("localhost\mysignalrhub").Build();
hubConnection.StartAsync().Wait();
_connection = connection;
_connection.OnUserUpdated((e) =>
{
hubConnection.InvokeAsync("Send", e).Wait();
});
_connection.Connect();
}
}
Some documentation: https://learn.microsoft.com/en-us/aspnet/core/signalr/dotnet-client?view=aspnetcore-2.1
Upvotes: 0
Reputation: 101
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");
Upvotes: 10
Reputation: 614
The answer is implictly given by this issue on github https://github.com/aspnet/Docs/issues/6888. It was an "issue" in the documentation, so what I tried to do is for sure not possible (it does not mention how to do it however).
It seems the hub class instance in a whole only lives when a call has been made, and as such you can't use the callbacks.
Upvotes: 0