SignalR’s real-time messaging bindings are a surprisingly powerful way to inject dynamic, live updates into your .NET applications without getting bogged down in WebSocket protocol details.

Let’s see it in action. Imagine a simple chat application.

// Server-side (ChatHub.cs)
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

// Client-side (JavaScript in an HTML file)
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .build();

connection.on("ReceiveMessage", (user, message) => {
    const msg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    const encodedMsg = user + " says " + msg;
    const liElement = document.createElement("li");
    liElement.textContent = encodedMsg;
    document.getElementById("messagesList").appendChild(liElement);
});

connection.start().catch(error => console.error(error.toString()));

In this example, SendMessage is a method exposed by the ChatHub on the server. When a client calls this method, SignalR broadcasts the user and message to all connected clients. On the client side, connection.on("ReceiveMessage", ...) is a listener that fires whenever a ReceiveMessage event is broadcast from the server. The JavaScript then updates the UI in real-time.

SignalR abstracts away the complexity of managing persistent connections, whether they’re WebSockets, Server-Sent Events, or Long Polling. It negotiates the best available transport and provides a unified API. The Hub class is the core of this; it’s a pipeline for server-to-client and client-to-server communication. Methods defined on the Hub can be invoked by clients, and methods on the Hub can invoke methods on connected clients.

The Clients property within a Hub is your gateway to broadcasting. Clients.All sends to everyone. Clients.Client(connectionId) sends to a specific user. Clients.Group(groupName) sends to a named group of clients. You can also manage groups dynamically using Groups.AddToGroupAsync(connectionId, groupName) and Groups.RemoveFromGroupAsync(connectionId, groupName). This allows for targeted real-time updates, like sending a notification only to users in a specific room or subscribed to a particular topic.

When you configure SignalR in your Startup.cs (or Program.cs in newer .NET versions), you’re essentially telling the application how to handle these persistent connections.

// Startup.cs (older .NET Core)
public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR();
    // ... other services
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ... other middleware
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/chathub"); // This maps the ChatHub to the /chathub URL
        // ... other endpoints
    });
}

// Program.cs (ASP.NET Core 6+)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
// ... other services
var app = builder.Build();
// ... other middleware
app.MapHub<ChatHub>("/chathub"); // This maps the ChatHub to the /chathub URL
// ... other endpoints

The MapHub<ChatHub>("/chathub") line is crucial. It registers your ChatHub with the framework and makes it accessible at the /chathub endpoint. Clients will connect to this URL to establish their real-time link. The withUrl("/chathub") in the JavaScript client corresponds directly to this server-side mapping.

What most people don’t realize is that SignalR doesn’t force WebSockets. It’s a fallback mechanism. If a client’s browser or network environment doesn’t support WebSockets (or if it’s blocked by a firewall), SignalR will gracefully degrade to Server-Sent Events (SSE) or, as a last resort, a two-second interval long polling HTTP request. This makes your real-time features robust across a wide range of client conditions without you needing to write separate logic for each transport.

The next step is exploring how to scale SignalR applications, particularly with backplanes like Redis to support distributed environments.

Want structured learning?

Take the full Azure-functions course →