SignalR Messaging Overview: A Look at How It All Hangs Together

SignalR is a fantastic library for enabling real-time applications, especially when using MVC or Blazor in ASP.NET Core. I’ve been using it a lot lately and while it is an excellent way to make your application feel super responsive through messaging and remote procedure calls, there are certain aspects of how the whole thing hangs together which can be very confusing.

This blog post is designed to give you an overview of the different parts of a “standard” SignalR implementation and explain how messages flow through the system. I won’t be going into details on how SignalR works internally (if I even could!), instead choosing to focus on the parts that you’ll come into direct contact with. This blog post also isn’t designed as a tutorial since there is great documentation for implementing SignalR already (which is where I sourced the below code examples from!).

SignalR Messaging Overview

I’ve put together a solution with the associated example SignalR implementation I’ve referenced below in case you wanted to see how it all hangs together in the context of a full project.

Let’s imagine that we have a chat application where different users can all connect to a chat room to send and receive chat messages. Let’s also assume that we’re using a combination of ASP.NET MVC for the server and JavaScript for interactive functionality on the clients. What does the SignalR lifecycle look like in this instance?

A flow-chart representing the route a SignalR message takes through a standard SignalR implementation. A client browser is on the left, with a group called "MVC App" containing a Request Handler and ChatHubInstance. The flow goes starts with the request handler mapping the ChatHubInstance to the endpoint /ChatHubEndpoint. The client then connects to the SignalR hub using the defined endpoint and transmits a "SendChatMessage" message to the hub endpoint, which is translated into an actual procedure called SendChatMessage on the mapped hub. The SendChatMessage procedure then sends a "ReceiveChatMessage" message to all clients, which each invoke their "ReceiveChatMessage" handler on receipt.

By the way, there’s a glossary at the end of this post with a summary of the parts of a SignalR system in case the terms start to all blend together!

Steps

1. Hub is mapped

The server maps a Hub instance to a specific endpoint URL. In our example the Hub instance is ChatHubInstance and the endpoint is “/ChatHubEndpoint”:

app.MapHub<ChatHubInstance>("/ChatHubEndpoint");

2. Client connects to hub

A client (e.g. one of the users in the chat room) connects to the SignalR Hub in JavaScript by specifying the mapped endpoint:

var connection = new signalR.HubConnectionBuilder().withUrl("/ChatHubEndpoint").build();

3. Client sends SignalR message

The connected client types out a chat message and hits the Send button which invokes a remote procedure by sending a SignalR message providing the name of the procedure (”SendChatMessage”) and the required arguments (in this case user and chatMessage):

connection.invoke("SendChatMessage", user, chatMessage)

4. Server translates SignalR message to procedure call

The server receives the request and handles it by mapping to an actual procedure in C#. In this case, it will invoke the SendChatMessage procedure in ChatHubInstance, passing in user and chatMessage as the arguments:

Task SendChatMessage(string user, string chatMessage)

5. Hub procedure sends SignalR message to clients

The implementation of SendChatMessage sends a SignalR message called “ReceiveChatMessage” to all clients with the same arguments that were originally passed in (user and chatMessage):

public async Task SendChatMessage(string user, string chatMessage)
{
    await Clients.All.SendAsync("ReceiveChatMessage", user, chatMessage);
}

6. Client handler invoked

The configured “ReceiveChatMessage” handler on each client executes (which in this case visually adds the latest chat message that was just received to the list):

connection.on("ReceiveChatMessage", function (user, message) {
    var li = document.createElement("li");
    document.getElementById("messagesList").appendChild(li);
    li.textContent = `${user} says ${message}`;
});

And that’s it! Things can change a little bit when you start using some of the other features of SignalR such as client groups, multiple hubs and Azure SignalR Service, but that’s the general flow for how a single SignalR message moves through a standard implementation!

Glossary

TermWhat is it?Notes
Hub/hub instanceAn instance of a class that inherits from Hub with defined procedures that can be invoked remotelyThe Hub we’ll be using in our examples is ChatHubInstance
Hub endpointThe address where the Hub is accessible fromThe Hub instance in our example is being hosted at “/ChatHubEndpoint”, which is where the clients will connect to
Hub mappingThe configuration in the application setup/startup that links the hub instance to the hub endpointIn our ASP.NET MVC example a client connecting to “/ChatHubEndpoint” will connect to an instance of ChatHubInstance, which is configured as follows:
app.MapHub<ChatHubInstance>("/ChatHubEndpoint");
MessageThe pieces of data being sent to/received from the SignalR HubWhere necessary I’ve tried to clarify when something is a SignalR message (a signal sent through SignalR between server and client(s)) or a chat message (the text someone sends in our example chat application, such as “Hello!”)
ProcedureOne or more lines of code that will be executed. Also known as a method, function or subroutine (depending on the language)I’ve used “procedure” throughout to tie into the concept of remote procedure calls
ClientsThe entities (e.g. users through their browser, background services on the server etc.) that are connected to the SignalR Hub and send/receive messagesIn this example the clients are the users connected to the chat room

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.