SignalR in ASP.NET Core
Introduction
SignalR is a Microsoft .NET library for implementing real-time web sites. It uses a number of techniques to achieve bi-directional communication between server and client; servers can push messages to connected clients anytime.
It was available in pre-Core ASP.NET and now a pre-release version was made available for ASP.NET Core. I already talked a few times about SignalR.
Installation
You will need to install the Microsoft.AspNetCore.SignalR.Client and Microsoft.AspNetCore.SignalR Nuget pre-release packages. Also, you will need NPM (Node Package Manager). After you install NPM, you need to get the @aspnet/signalr-client package, after which, you need to get the signalr-client-1.0.0-alpha1-final.js file (the version may be different) from the node_modules\@aspnet\signalr-client\dist\browser folder and place it somewhere underneath the wwwroot folder, so that you can reference it from your pages.
Next, we need to register the required services in ConfigureServices:, before Use
services.AddSignalR();
We will be implementing a simple chat client, so, we will register a chat hub, in the Configure method:
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("chat");
});
A note: UseSignalR must be called before UseMvc!
You can do this for any number of hubs. as long as you have different endpoints. More on this in a moment.
In your view or layout file, add a reference to the signalr-client-1.0.0-alpha1-final.js file:
<script src="libs/signalr-client/signalr-client-1.0.0-alpha1-final.js"></script>
Implementing a Hub
A hub is a class that inherits from (you guessed it) Hub. In it you add methods that may be called by JavaScript. Since we will be implementing a chat hub, we will have this:
public class ChatHub : Hub
{
public async Task Send(string message)
{
await this.Clients.All.SendAsync("Send", message);
}
}
As you can see, we have a single method, Send, which, for this example, takes a single parameter, message. You do not need to pass the same parameters on the broadcast call (SendAsync), you can send whatever you want.
Note: in previous versions this method was called InvokeAync!
Going back to the client side, add this code after the reference to the SignalR JavaScript file:
<script>
var transportType = signalR.TransportType.WebSockets;
//can also be ServerSentEvents or LongPolling
var logger = new signalR.ConsoleLogger(signalR.LogLevel.Information);
var chatHub = new signalR.HttpConnection(`http://${document.location.host}/chat`, { transport: transportType, logger: logger });
var chatConnection = new signalR.HubConnection(chatHub, logger);
chatConnection.onClosed = e => {
console.log('connection closed');
};
chatConnection.on('Send', (message) => {
console.log('received message');
});
chatConnection.start().catch(err => {
console.log('connection error');
});
function send(message) {
chatConnection.invoke('Send', message);
}
</script>
Notice this:
-
A connection is created pointing to the current URL plus the chat suffix, which is the same that was registered in the MapHub call
-
It is initialized with a specific transport, in this case, WebSockets, but this is not required, that is, you can let SignalR figure out for itself what works; for some operating systems, such as Windows 7, you may not be able to use WebSockets, so you have to pick either LongPolling or ServerSentEvents
-
The connection needs to be initialized by calling start
-
There is an handler for the Send method which takes the same single parameter (message) as the ChatHub’s Send method
It is possible to pass arbitrary query string parameters that can be later caught in a hub, in fact, this is the only way to pass data to a hub:
var chatHub = new signalR.HttpConnection(`http://${document.location.host}/chat?key=value`, { transport: transportType, logger: logger });
On the server, use this:
var value = this.Context.Connection.GetHttpContext().Request.Query["key"].SingleOrDefault();
So, whenever someone accesses this page and calls the JavaScript send function, it invokes the Send method on the ChatHub class. This class basically broadcasts this message to all connected clients (Clients.All). It is also possible to send messages to a specific group (we’ll see how to get there):
await this.Clients.Group("groupName").SendAsync("Send", message);
await this.Clients.Client("id").SendAsync("Send", message);
public override Task OnConnectedAsync()
{
this.Groups.AddAsync(this.Context.ConnectionId, "groupName");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
return base.OnDisconnectedAsync(exception);
}
Another example, imagine you wanted to send timer ticks into all connected clients, through a timer hub. You could do this in the Configure method:
TimerCallback callback = (x) => {
var hub = serviceProvider.GetService<IHubContext<TimerHub>>();
hub.Clients.All.SendAsync("Notify", DateTime.Now);
};
var timer = new Timer(callback);
timer.Change(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(10));
public class TimerHub : Hub
{
}
Sending Messages Into a Hub
private readonly IHubContext<ChatHub> _context;
[HttpGet("Send/{message}")]
public IActionResult Send(string message)
{
//for everyone
this._context.Clients.All.SendAsync("Send", message);
//for a single group
this._context.Clients.Group("groupName").SendAsync("Send", message);
//for a single client
this._context.Clients.Client("id").SendAsync("Send", message);
return this.Ok();
}
Note that this is not the same as accessing the ChatHub class, you cannot easily do that, but, rather, the chat hub’s connections.