Suppose I have a ChatHub class defined along with a Broadcast method.
I know how to broadcast messages to all clients if one of them sends a message, however how can I send a message to all the clients from Global.asax ?
In other words how do I get access to ChatHub from another class?
Here's a basic sample:
public class ChatHub : Hub
{
public void Broadcast(String reqMessage)
{
Clients.broadcast(reqMessage);
}
}
Your help is much appreciated.
Since SignalR 0.5 you can do this using GlobalHost.ConnectionManager.GetHubContext
Sample
// get gub context
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
// broadcast to all clients in this hub
context.Clients.broadcast("Hello World");
More Information
SignalR - Hubs
Use ConnectionManager, as described here: https://github.com/SignalR/SignalR/wiki/Hubs.
Btw, your question is most likely a duplicate of this one.
Related
I am using SignalR v2.41, which is old, but I have to use it since I am also limited to using an old version of MVC. That aside, I am also using FluentScheduler to send targeted messages to clients at intervals.
Problem is, I am keeping a dictionary of user connections in my Hub:
public class MyHub: Hub
{
public Dictionary<string, User> Connections { get; set; }
public MyHub()
{
Connections = new Dictionary<string, User>();
}
public override Task OnConnected()
{
// add connection
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
// remove connection
return base.OnDisconnected(stopCalled);
}
}
Now in the FluentScheduler code I need to get hold of the hub for the connections list so I know which connection to send what to:
public class MyJob : IJob
{
public void Execute()
{
var hub = new DefaultHubManager(GlobalHost.DependencyResolver).ResolveHub("MyHub") as MyHub;
foreach (var conn in hub.Connections)
{
foreach (var msg in msgs)
{
hub.Clients.Client(conn.Key).send(msg);
}
}
}
}
Problem is, the hub instance I get using var hub = new DefaultHubManager(GlobalHost.DependencyResolver).ResolveHub("MyHub") as MyHub; is different from the one to which clients connect, as this one never has any connections.
How can I get the right hub instance?
The new is always a new instance so you will never get the hub where your clients are connected because you creating a new hub.
You should resolve the hub like this:
static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
You also can check this question.
Edit: Since you need to send messages to specific users, I would recommend to implement a class to add and remove the connections, or even better, map users to groups.
It's recommend that you always inject IHubContext than the Hub. Quote from a SignalR developer on github:
You generally shouldn't resolve the Hub out of DI. If you need to share code between your Hub and some other component, I'd suggest using either IHubContext or putting the shared code in a separate DI service instead.
Also you should not add the Hub as a singleton:
SignalR expects the Hub to be created separately for each message. You need to add it as a Transient service if you want your Hub to be in DI.
and
Because instances of the Hub class are transient, you can't use them to maintain state from one method call to the next. Each time the server receives a method call from a client, a new instance of your Hub class processes the message. To maintain state through multiple connections and method calls, use some other method such as a database, or a static variable on the Hub class, or a different class that does not derive from Hub.
More documentation about Hub object lifetime.
It seems like a big use for SignalR Hubs is to display the actions of one client to all of the other clients. What I hope to use SignalR for is when a certain event happens in my server side code, I want to instantiate a hub object and invoke one of its methods to communicate with all of the clients. If you see my previous post (Route To Take With SqlDependency OnChange), I would like to do this in the OnChange method of SqlDependency. Upon researching it I have found some people talk about using an IHubContext object, though I haven't found many examples of instantiation and actual sending data to clients.
Is this possible to do (and what might sending data to all clients with IHubContext look like if possible), and if not, are there any ways I might be able to get around instantiating a hub like this?
SignalR for ASP.NET Core
You can create a class that has the IHubContext<T> injected in. Inject other dependencies if you want, or resolve the service from controllers or other classes.
public class NotificationService
{
private readonly IHubContext<MyHub> _myHubContext;
public NotificationService(IHubContext<MyHub> myHubContext)
{
_myHubContext= myHubContext;
}
public async Task SendMessage(string message)
{
await _myHubContext.Clients.All.SendAsync("Update", message);
}
}
Assuming you're using SqlDependency from an IHostedService:
public class MyHostedService : IHostedService
{
public MyHostedService(
NotificationService notificationService)
{
// TODO get reference to sqlDependency
sqlDependency.OnChange += (s, e) => _notificationService.SendMessage(e.Info.ToString());
}
}
SignalR for ASP.NET
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.All.sendMessage(message);
You need to use using Microsoft.AspNet.SignalR library.
using Microsoft.AspNet.SignalR;
//Instantiating. SignalRHub is the hub name.
var context = GlobalHost.ConnectionManager.GetHubContext<SignalRHub>();
//sends message
context.Clients.Client(ClientId).sendMessage(data);
I've successfully setup a SignalR server and client using the newly released ASP.NET Core 2.1. I built a chat room by making my ChatHub extend Hub: whenever a message comes in from a client, the server blasts it back out via Clients.Others.
What I do not yet understand is how to send a message to clients not as a response to an incoming message. If the server is doing work and produces a result, how do I gain access to the Hub in order to message particular clients? (Or do I even need access to the Hub? Is there another way to send messages?)
Searching this issue is difficult as most results come from old versions of ASP.NET and SignalR.
You can inject the IHubContext<T> class into a service and call the clients using that.
public class NotifyService
{
private readonly IHubContext<ChatHub> _hub;
public NotifyService(IHubContext<ChatHub> hub)
{
_hub = hub;
}
public Task SendNotificationAsync(string message)
{
return _hub.Clients.All.SendAsync("ReceiveMessage", message);
}
}
Now you can inject the NotifyService into your class and send messages to all clients:
public class SomeClass
{
private readonly NotifyService _service;
public SomeClass(NotifyService service)
{
_service = service;
}
public Task Send(string message)
{
return _service.SendNotificationAsync(message);
}
}
Simple inject the hubcontext into the class where you use the hubcontext.
Details you will find there:
Call SignalR Core Hub method from Controller
There are now official Microsoft Docs for the SignalR HubContext that answer your question
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1
But yes, as others have pointed out, you need to get an instance of IHubContext via dependency injection to access hub methods outside of the hub.
According to the documentation I have read, in order to send a message to a client, I just need to call:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.All.foo(msg);
So... does the Hub class need any methods? If not, then all I have is an empty class:
public class MyHub : Hub
{
}
which just seems like a pointless setup. Am I implementing this incorrectly? because it makes more sense to have methods in a hub class, and then call those methods to send a message to the client.
Also, in the hub itself, I can access Context.connectionId, so that I can get the requestor's connection Id and then stop the message from being fired to that client.... If a Hub shouldn't have methods, then is there a way to access the requestor's connection id?
Yes, you need an empty HUB class declaration, because - It is actually just a proxy between the JS client and the controller so it could be empty since all methods are called via the Clients dynamic variable.
I mean without this , you can work, but you have to write JS for that. This is explained briefly in following link.
For more info refer this link - http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-javascript-client#genproxy
I'm wondering if it's possible to Cast the result of
var hub = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
To my actual ChatHub class.
Because GlobalHost.ConnectionManager.GetHubContext<ChatHub>() as ChatHub fails
On my ChatHub class I have a method UpdateTime():
public void SendTimeUpdate(DateTime time, string auth)
{
Clients.All.UpdateTime(time, auth);
}
And I want to call it from my other class. Since I can't cast to ChatHub and invoke the SendUpdate I have to go:
GlobalHost.ConnectionManager.GetHubContext<ChatHub>().Clients.All.UpdateTime(time, auth);
But if I go this road, the method SendTimeUpdate isn't added in the proxy script /signalr/hubs
Is there a solution for this problem? I want to get the typed Hub instance and not call stuff directly on the Clients property of the IHubContext.
No you cannot cast the result of ....GetHubContext<.... to your hub class. Sorry :(.
The GetHubContext approach returns an IHubContext when a Hub is only an IHub.
If you'd like to centralize the logic just make a method that you can call into from your hub and from your external service.
Couldn't your class just create a connection to your hub and call the method that way?