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);
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.
I have a scoped service:
public class GetLatestStatus:IGetLatestStatus{
private HttpClient _httpClient;
private readonly int _status;
public GetLatestStatus(HttpClient httpClient){
_httpClient = httpClient;
_date= GetStatusFromService();
}
public string GetStatus(){
return _status;
}
private string GetStatusFromService(){
Logger.Info($"Calling service...");
var request = new HttpGetRequest{Url = "http://some.service/get/status"};
var result = _httpClient.Get(request).Result;
return result.Status;
}
}
Here is how it is defined in the startup:
public virtual void ConfigureServices(IServiceCollection services){
services.AddScoped<IGetLatestStatus, GetLatestStatus>()
.AddHttpClient<IGetLatestStatus, GetLatestStatus>();
services.AddTransient<ISomeClass1, SomeClass1>();
services.AddTransient<ISomeClass2, SomeClass2>();
services.AddTransient<ISomeClass3, SomeClass3>();
}
It is being used by three transient classes.
The intent of this class is that _status is defined only once, when the request comes in. Then it is stored throughout the lifecycle of the request.
Instead, it seems that GetStatusFromService() is being called three times, one per transient class, when the request first comes in.
How do I make this class work the way I intended? I thought that defining something as a Scoped Service means that there's only one copy for the lifecycle of the request. Thank you all for the help!
TL:DR
It happens because you register GetLatestStatus like this after scoped registration .AddHttpClient<IGetLatestStatus, GetLatestStatus>();
So may create another class to store the status and register it as scoped. Then use the Http Configured service to reach the service from it
According to MSDN;
To configure the above structure, add HttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet package that includes the AddHttpClient() extension method for IServiceCollection. This extension method registers the DefaultHttpClientFactory to be used as a singleton for the interface IHttpClientFactory. It defines a transient configuration for the HttpMessageHandlerBuilder. This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.
Please check the link for more information https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
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.
I'm trying to share the elements in cache between ServiceStack OOB ICacheClient and a SignalR Hub, but I'm getting the following error when I try to get the user session in the OnDisconnected event
Only ASP.NET Requests accessible via Singletons are supported
I have no issues accessing the session in the OnConnected event, so far this is what I've done:
public class HubService:Hub
{
private readonly IUserRepository _userRepository;
private readonly ICacheClient _cacheClient;
public HubService(IUserRepository userRepository,ICacheClient cacheClient)
{
_userRepository = userRepository;
_cacheClient = cacheClient;
}
public override System.Threading.Tasks.Task OnConnected()
{
var session = _cacheClient.SessionAs<AuthUserSession>();
//Some Code, but No error here
return base.OnConnected();
}
public override System.Threading.Tasks.Task OnDisconnected()
{
var session = _cacheClient.SessionAs<AuthUserSession>();
return base.OnDisconnected();
}
}
I'm using simple injector and my ICacheClient is registered as singleton:
Container.RegisterSingle<ICacheClient>(()=>new MemoryCacheClient());
the question is how do I register requests as singletons in SS? what am I missing on SignalR event?
Edit:
what I tried to expain for register requests in SS is because if there's a possibility to register SS IHttpRequest using a container and set the lifestyle as singleton due to the exception message, it seems like httpContext and IHttprequest are null by the OnDisconnected event
the SS code is the following:
public static string GetSessionId(IHttpRequest httpReq = null)
{
if (httpReq == null && HttpContext.Current == null)
throw new NotImplementedException(OnlyAspNet); //message
httpReq = httpReq ?? HttpContext.Current.Request.ToRequest();
return httpReq.GetSessionId();
}
what I'm trying to do is to store a list of connected users using ICacheClient and I just want to remove the connectionID from the list when a user get disconnected.
Edit:
it seems like according to danludwig post
"There is an interesting thing about SignalR... when a client disconnects from a
hub (for example by closing their browser window), it will create a
new instance of the Hub class in order to invoke OnDisconnected().
When this happens, HttpContext.Current is null. So if this Hub has any dependencies that are >registered per-web-request, something will probably go wrong."
the description above perfectly match my situation
I am no SingalR expert, but based on my experience with it and simple injector, I don't think you can get at Session (or Request, or HttpContext for that matter) during OnDisconnected. It kind of makes sense if you think about it though -- when a client disconnects from a hub, SignalR no longer has access to a session ID, there is no request, there is no more communication with the client. OnDisconnected is basically telling you "here, do something with this ConnectionId because the client it belonged to has gone away." Granted the user may come back, and then you can get access to the web goodies (session, request, etc, as long as you are IIS hosted) during OnReconnected.
I was having similar problems getting some simpleinjector dependencies to have correct lifetime scope during these 3 Hub connection events. There was 1 dependency that I wanted to register per http request, and it worked for everything except OnDisconnected. So I had to fenagle the SI container to use http requests when it could, but use a new lifetime scope when the dependency was needed during an OnDisconnected event. If you care to read it, I have a post here that describes my experiences. Good luck.
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.