SignalR - HubContext and Hub.Context - asp.net

I am new to signalR and reading the API and playing around with it. a bit confused about the Hub and its Context.
That is, Hub.Context is not HubContext.
HubContext I can get from GlobalHost.ConnectionManager.GetHubContext<THub>()
and Hub.Context gives me a HubCallerContext which I am not sure how to use.
What are their relationship? How can I get HubContext from Hub or Hub from HubContext ?

A result of poor naming. Hub.Context is the HTTP context from the caller (more like a request context). The HubContext has the GroupManager and Clients which map to Hub.Groups and Hub.Clients.
You can add to groups and talk to the client from outside the hub. Inside the hub you can get the connection ID of the caller and get the HTTP request context associated with the hub invocation. Outside of the hub, you can't do Context.Clients.Caller or Context.Clients.Others because there's no caller when you're outside of the hub.
Hope that clears things up.

The HubCallerContext is a context that is relative to the current request. You would not be able to do the following using a HubContext:
public class MyHub : Hub
{
public void Foo()
{
// These two are equivalent
Clients.Caller.bar();
Clients.Client(Context.ConnectionId).bar(); // Context.ConnectionId is the client that made the request connection id
}
}
The reason why you would not be able to do these with the HubContext is because you do not have a Clients.Caller and you do not have a Context.ConnectionId.
You can however do everything that you can do with a HubContext with a HubCallerContext.
Think of a HubCallerContext as a request relative, more advanced version of HubContext.
Ultimately HubContext is used when you want to send data to a hubs clients outside of the context of a request.
Hope this helps!

Related

Understanding SignalR- Hubs, Dependency Injection, and Controllers

I've been pouring through documentation and forums trying to understand SignalR but I'm pretty stuck.
What I'm trying to achieve is, in a chat application: store messages outside of the Hub so that each time a user joins the chat, they can see all messages that had been sent before they joined.
So it seemed like an external class was the way to do that so I got that working with dependency injection
In ChatHub.cs
namespace SignalRChat.Hubs
{
public class ChatHub: Hub
{
public IChatStorage _chatStorage;
public ChatHub(IChatStorage chatStorage)
{
_chatStorage = chatStorage;
}
// and so on
And I have a method in ChatHub to send a message to chatStorage, but I'm confused on how to send back a list of all messages from chatStorage to ChatHub, or even to JavaScript. It seemed like a Controller was the way to do that but I'm not sure how to call the controller's methods:
namespace SignalRChat.Controllers
{
public class ChatController: Controller
{
private IHubContext<ChatHub> _hubContext;
public ChatController(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
public void Send(List<Message> messages)
{
// to do: something where chatStorage calls this method, then this
// method uses _hubContext.Clients.All.SendAsync
// But, how do I even call Send()???
}
}
}
Fundamentally I just don't understand how to wire everything up. SignalR is just really confusing.
I get how the simple server Hub and client JavaScript relationship works. But, then with dependency injection I don't get why
public ChatHub(IChatStorage chatStorage)
{
_chatStorage = chatStorage;
}
works. I didn't change any code to say like new ChatHub(new IChatStorage). Microsoft's docs even say that SignalR only calls default Hub constructors.
In Startup.cs nothing seems to specify that I want to call ChatHub with a new chatStorage:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR();
services.AddSingleton<IChatStorage, ChatStorage>();
services.AddSingleton<ChatController>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// omitted some default code
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/chatHub");
});
}
So first question, how does that work? How does it know to pass an argument to the ChatHub constructor? I understand the services.AddSingleton part, just not how that gets "wired up".
Same thing with a controller class. How does the program know to pass an IHubContext object into its constructor? Where do you specify that?
Finally, how would you go about making this setup work? Current I'm trying to communicate from ChatHub->chatStorage->ChatController->ChatHub. And to do that I'm trying to pass a reference to chatController in chatStorage.
Not sure if it's clear what I'm asking. If anything I'm looking for a clear explanation on how these concepts all work together, rather than a specific solution to my code.
Thank you!
So first question, how does that work? How does it know to pass an argument to the ChatHub constructor? I understand the services.AddSingleton part, just not how that gets "wired up".
When SignalR instantiates your hub instance (the framework controls this, you don't), it will resolve any dependencies specified in your Hub's constructor. This is part of the dependency injection system that's part of .NET (as mentioned in the comments).
Same thing with a controller class. How does the program know to pass an IHubContext object into its constructor? Where do you specify that?
Same idea, but you didn't have to wire up IHubContext, that's something that AddSignalR does.
Finally, how would you go about making this setup work? Current I'm trying to communicate from ChatHub->chatStorage->ChatController->ChatHub. And to do that I'm trying to pass a reference to chatController in chatStorage.
It's not exactly clear to me what you want the interaction to be between the client and the server to both the hub and the controller.
What I'm trying to achieve is, in a chat application: store messages outside of the Hub so that each time a user joins the chat, they can see all messages that had been sent before they joined.
Going back to this original statement I can ask a few clarifying questions:
When the user writes a message in the chat room, are you calling the hub or a controller action (REST API)? That will determine where you need to inject the IChatStorage type. Once you receive a message, you'll stash the message in IChatStorage.
When the user joins the chat, they'll make a call to the server to retrieve all messages. This call will return messages stored in IChatStorage.
Assuming you want to use the hub for everything, you would expose methods on the hub do accomplish this. If you wanted to use REST API calls from the client, then you would use the controller.

Filter Outgoing SignalR Core Hub Messages

I am using SignalR and .Net 5.0 and leveraging Hub Filters to execute code on incoming Invokations to my SignalR Hub.
I am looking for a way to do the same thing with outgoing messages from the Hub to the Client but seem to be coming up with no options.
Perhaps alternatively, I would love to hook into and execute code specifically when the built in Ping Messages are sent out.
It looks like similar functionality used to be possible in the old version's HubPipeLineModule but I have not been able to find any way to achieve this in current SignalR. Is this possible?
HubLifetimeManager is responsible for sending messages to clients and it is possible to inject your custom one, right before registering SignalR:
builder.Services.AddSingleton(typeof(HubLifetimeManager<>), typeof(CustomHubLifetimeManager<>));
builder.Services.AddSignalR();
where
public class CustomHubLifetimeManager<THub> : DefaultHubLifetimeManager<THub> where THub : Hub
{
public override Task SendAllAsync(string methodName, object?[] args, CancellationToken cancellationToken = default)
{
//todo: do your stuff
return base.SendAllAsync(methodName, args, cancellationToken);
}
//todo: override rest of the methods
}
It works fine, however this approach looks a bit hacky for me.

How do I maintain a list of Client methods the Server's Hub can call?

For SignalR 2.1, how do I maintain a list of Client methods the Server's Hub can call?
According to Differences between SignalR and ASP.NET Core SignalR - Hub Proxies, "Hub proxies are no longer automatically generated."
Is there an existing solution to maintain a list of Client methods the Server's Hub can call?
Looking for a solution that defines Client methods to be called by Server Hub before we decide to roll our own with Code Generation.
Looks like Hub and IHubContext take T type parameter for the type of client that you can make an interface for. Can't find any documentation specific to dotnet core other than the source code and comments but look like this is a carry over from .net
https://blog.scottlogic.com/2014/08/08/signalr-typed.html -> "Calling client hubs - New and Improved"
public interface IMyHubClient
{
void Ping();
}
public class MyHub : Hub<IMyHubClient>
{
...
}

SignalR 2 connection not being persisted

I've set up a sample SignalR hub, ChatHub, added list of connections. When it runs OnConnected I see it being add to the List. However when I open that page in another browser (expecting the list to have 2 connections now I see 0 connections in my list). Is ChatHub instantiated per request?
List<string> connections = new List<string>();
public override Task OnConnected()
{
connections.Add(Context.ConnectionId);
return base.OnConnected();
}
Yes a Hub instance is created for each request.
specifically:
You don't instantiate the Hub class or call its methods from your own
code on the server; all that is done for you by the SignalR Hubs
pipeline. SignalR creates a new instance of your Hub class each time
it needs to handle a Hub operation such as when a client connects,
disconnects, or makes a method call to the server.

How do you get HubContext in SignalR 3?

In SignalR 2 you could do something like this (taken from my blog):
var stockTickerHub = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
That allows you to get a reference to the SignalR hub from outside the hub (e.g. from a stock ticker thread).
This does not seem to be available in SignalR 3. How do you achieve the equivalent functionality in the new and upcoming SignalR?
I asked the same thing to the creator of SignalR, David Fowler on Jabbr, a forum where the creators of SignalR and the architects of ASP.NET 5 hang on from time to time, and his answer to this question was to use dependency injection.
While I haven't tried it yet with SignalR 3, I am pretty sure you can inject an instance of ConnectionManager that implements IConnectionManager in your class, and use it just like you would use GlobalHost to resolve your hub context.
Again, I have not done this with SignalR3, but I hope this will get you a little closer to finding a solution.
I put together a sample for using SignalR 2 with Autofac. (In this repo I use Autofac to inject dependencies in my hub, but also to inject instances of a ConnectionManager in other classes to get the hub context).
Hope this helps. Best of luck!
Dependency injection is indeed the way and works.
Example:
public class ChatController : Controller
{
readonly IConnectionManager _connectionManager;
public ChatController(IConnectionManager connectionManager)
{
_connectionManager = connectionManager;
}
public IActionResult Chat(string message)
{
IHubContext context = _connectionManager.GetHubContext<ChatHub>();
IConnection connection = _connectionManager.GetConnectionContext<PersistentConnection>().Connection;
context.Clients.All.NewMessage(message);
return new EmptyResult();
}
}
From every example I have seen and the few SignalR 3 apps I have implemented, you no longer have a strongly typed reference to your hub. The current methodology connects to a hub via the hub's name and URL. The On generic method creates a subscription to broadcasts from that hub and the method name you provide.
HubConnection connection = new HubConnection(ServerURL);
IHubProxy hubProxy = connection.CreateHubProxy("StockTickerHub");
hubProxy.On<StockTickerMessage>("[Your method name here]", msg => {
//your UI update logic here
});

Resources