Adding Real-Time Functionality To .NET Applications With SignalR

Adding Real-Time Functionality To .NET Applications With SignalR

6 min read ·

Thank you to our sponsors who keep this newsletter free to the reader:

Today's issue is sponsored by Treblle. Treblle is a lightweight SDK that helps engineering and product teams build, ship & maintain REST-based APIs faster. Simple integration for all popular languages & frameworks, including .NET 6.

And by Fiverr. Fiverr is your one-stop solution for finding skilled Web application developers. Build your custom Web application from scratch with help from skilled freelance coders. Check it out here!

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 with it.

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.


Whenever you're ready, there are 4 ways I can help you:

  1. Modular Monolith Architecture (NEW): This in-depth course will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario. Join 2,800+ students here.
  2. 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 2,800+ students here.
  3. Patreon Community: Join a community of 1,050+ engineers and gain access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses. Join 1,050+ engineers here.
  4. Promote yourself to 48,000+ subscribers by sponsoring this newsletter.

Become a Better .NET Software Engineer

Join 48,000+ engineers who are improving their skills every Saturday morning.