Today's modern applications must deliver the latest information without refreshing the user interface.
If you need to introduce real-time functionality to your application in .NET, there's one library you will most likely reach for - SignalR.
SignalR allows you to push content from your server-side code to any connected clients as changes happen in real-time.
Here's what I'll teach you in this week's newsletter:
- Creating your first SignalR
Hub
- Testing SignalR from Postman
- Creating strongly typed hubs
- Sending messages to a specific user
Let's see why SignalR is so powerful and how easy it is to build real-time applications.
Installing And Configuring SignalR
To start using SignalR you'll need to:
- Install the NuGet package
- Create the
Hub
class - Register the SignalR services
- Map and expose the hub endpoint so clients can connect to it
Let's start by installing the Microsoft.AspNetCore.SignalR.Client
NuGet package:
Install-Package Microsoft.AspNetCore.SignalR.Client
Then you need a SignalR Hub
, which is the central component in your application responsible for managing clients and sending messages.
Let's create a NotificationsHub
by inheriting from the base Hub
class:
public sealed class NotificationsHub : Hub
{
public async Task SendNotification(string content)
{
await Clients.All.SendAsync("ReceiveNotification", content);
}
}
The SignalR Hub
exposes a few useful properties:
-
Clients
- used to invoke methods on the clients connected to this hub -
Groups
- an abstraction for adding and removing connections from groups -
Context
- used for accessing information about the hub caller connection
You can learn more about the Hub
class here.
Lastly, you need to register the SignalR services by calling the AddSignalR
method. You also need to call the MapHub<T>
method, where you specify the NotificationsHub
class and the path clients will use to connect to the hub.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
var app = builder.Build();
app.MapHub<NotificationsHub>("notifications-hub");
app.Run();
Now let's see how we can test the NotificationsHub
.
Connecting To SignalR Hub From Postman
To test SignalR, you need a client that will connect to the Hub
instance. You could create a simple application with Blazor or JavaScript, but I will show you a different approach.
We will use Postman's WebSocket Request to connect to the NotificationsHub
.
Here's what we need to do:
- Connect to the
NotificationsHub
- Set the communication protocol to JSON
- Send messages to call the
NotificationsHub
methods
All messages need to end with a null termination character, which is just the ASCII character 0x1E
.
Let's start off by sending this message to set the communication protocol to JSON:
{
"protocol": "json",
"version": 1
}?
You'll receive this response from the hub.
We need a slightly different message format to call a message on the Hub
. The key is specifying the arguments
and target
, which is the actual hub method we want to call.
Let's say we want to call the SendNotification
method on the NotificationsHub
:
{
"arguments": ["This is the notification message."],
"target": "SendNotification",
"type": 1
}?
This will be the response we get back from the NotificationsHub
:
Strongly Typed Hubs
The base Hub
class uses the SendAsync
method to send messages to connected clients. Unfortunately, we have to use strings to specify client-side methods to invoke, and it's easy to make a mistake. There's also nothing enforcing which parameters are used.
SignalR supports strongly typed hubs that aim to solve this.
First, you need to define a client interface, so let's create a simple INotificationsClient
abstraction:
public interface INotificationsClient
{
Task ReceiveNotification(string content);
}
The arguments don't have to be primitive types and can also be objects. SignalR will take care of serialization on the client side.
After that, you need to update the NotificationsHub
class to inherit from the Hub<T>
class to make it strongly typed:
public sealed class NotificationsHub : Hub<INotificationsClient>
{
public async Task SendNotification(string content)
{
await Clients.All.ReceiveNotification(content);
}
}
You will lose access to the SendAsync
method, and only the methods defined in your client interface will be available.
Sending Server-Side Messages With HubContext
What good is a NotificationsHub
if we can't send notifications from the backend to connected clients?
Not much.
You can use the IHubContext<THub>
interface access to the Hub
instance in your backend code.
And you can use IHubContext<THub, TClient>
for a strongly typed hub.
Here's a simple Minimal API endpoint that injects an IHubContext<NotificationsHub, INotificationsClient>
for our strongly typed hub and uses it to send a notification to all connected clients:
app.MapPost("notifications/all", async (
string content,
IHubContext<NotificationsHub, INotificationsClient> context) =>
{
await context.Clients.All.ReceiveNotification(content);
return Results.NoContent();
});
Sending Messages To a Specific User
The real value of SignalR is being able to send messages, or notifications in this example, to a specific user.
I've seen some complicated implementations that manage a dictionary with a user identifier and a map of active connections. Why would you do that when SignalR already supports this functionality?
You can call the User
method and pass it the userId
to scope the ReceiveNotification
message to that specific user.
app.MapPost("notifications/user", async (
string userId,
string content,
IHubContext<NotificationsHub, INotificationsClient> context) =>
{
await context.Clients.User(userId).ReceiveNotification(content);
return Results.NoContent();
});
How does SignalR know which user to send the message to?
It uses the DefaultUserIdProvider
internally to extract the user identifier from the claims. To be specific, it's using the ClaimTypes.NameIdentifier
claim. This also implies that you should be authenticated when connecting to the Hub
, for example, by passing a JWT.
public class DefaultUserIdProvider : IUserIdProvider
{
public virtual string? GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
By default, all the methods in a hub can be called by unauthenticated users. So you need to decorate it with an Authorize
attribute to only allow authenticated clients to access the hub.
[Authorize]
public sealed class NotificationsHub : Hub<INotificationsClient>
{
public async Task SendNotification(string content)
{
await Clients.All.ReceiveNotification(content);
}
}
In Summary
Adding real-time functionality to your application creates room for innovation and adds value to your users.
With SignalR, you can start building real-time apps in .NET in minutes.
You need to grasp one concept - the Hub
class. SignalR abstracts away the message transport mechanism, so you don't have to worry about it.
Make sure to send authenticated requests to SignalR hubs and turn on authentication on the Hub
. SignalR will internally track the users connecting to your hubs, allowing you to send them messages based on the user identifier.
That's all for today.
Thanks for reading, and have an awesome Saturday.
Today's action step: Look at your project and try to find an opportunity to add real-time functionality. Commit 30 min. to build a simple proof of concept with SignalR and see if it can improve your project.
P.S. Whenever you're ready, there are 2 ways I can help you:
Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 1,000+ students here.
Patreon Community: Think like a senior software engineer with access to the source code I use in my YouTube videos and exclusive discounts for my courses. Join 850+ engineers here.