How to Build Real-Time SignalR Hubs in C
This article provides a comprehensive guide to understanding ASP.NET Core SignalR and implementing real-time, bidirectional communication in modern C# applications. You will learn the core concepts behind SignalR, how the framework abstracts transport protocols like WebSockets, and the exact steps required to implement, configure, and consume a SignalR Hub for instant client-server data exchange.
What is ASP.NET Core SignalR?
ASP.NET Core SignalR is an open-source library that simplifies adding real-time web functionality to applications. Real-time functionality enables server-side code to push content to connected clients instantly as events occur, eliminating the need for clients to constantly poll the server for updates.
SignalR automatically chooses the best available transport method based on server and client capabilities:
- WebSockets: The preferred, high-performance transport for full-duplex communication.
- Server-Sent Events (SSE): A fallback that allows the server to push events to the client over standard HTTP.
- Long Polling: A secondary fallback that holds an HTTP request open until data is available.
The central programming abstraction in SignalR is the Hub. A Hub is a high-level pipeline that enables connected servers and clients to call methods on each other directly, passing strongly typed parameters and handling complex serialization automatically.
Step 1: Create the SignalR Hub
A Hub is created by inheriting from the
Microsoft.AspNetCore.SignalR.Hub base class. Methods
defined within this class can be invoked directly by connected clients,
while the Clients property allows the server to invoke
methods on one, several, or all connected clients.
using Microsoft.AspNetCore.SignalR;
namespace RealTimeApp.Hubs;
public class ChatHub : Hub
{
// Method callable by clients to broadcast a message
public async Task SendMessage(string user, string message)
{
// "ReceiveMessage" is the client-side handler name
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// Optional lifecycle hooks
public override async Task OnConnectedAsync()
{
await Clients.Caller.SendAsync("ReceiveMessage", "System", "Connected successfully.");
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
await base.OnDisconnectedAsync(exception);
}
}Strongly Typed Hubs (Recommended)
To avoid magic strings like "ReceiveMessage", define a
client contract interface and inherit from
Hub<T>:
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
public class StronglyTypedChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.ReceiveMessage(user, message);
}
}Step 2: Register and Map the Hub in Program.cs
SignalR requires two primary configuration steps in an ASP.NET Core application: registering the SignalR services in the dependency injection container and mapping the Hub endpoint to an incoming route.
Open Program.cs and add the following
configurations:
using RealTimeApp.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Register SignalR services
builder.Services.AddSignalR();
var app = builder.Build();
app.UseRouting();
// Map the Hub to a public URL endpoint
app.MapHub<ChatHub>("/chatHub");
app.Run();Step 3: Connect and Interact from a Client
Clients can be built using various technologies, including JavaScript/TypeScript, .NET, Java, and Python. Below are examples demonstrating bidirectional communication using both JavaScript and C#.
JavaScript Client Example
Using the @microsoft/signalr npm package or CDN:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.withAutomaticReconnect()
.build();
// Listen for messages pushed from the server
connection.on("ReceiveMessage", (user, message) => {
console.log(`${user}: ${message}`);
});
// Start the connection and send a message to the server
async function start() {
try {
await connection.start();
console.log("Connected to SignalR Hub");
// Invoke the server-side SendMessage method
await connection.invoke("SendMessage", "Alice", "Hello World!");
} catch (err) {
console.error(err);
}
}
start();C# Client Example
Using the Microsoft.AspNetCore.SignalR.Client NuGet
package:
using Microsoft.AspNetCore.SignalR.Client;
var connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/chatHub")
.WithAutomaticReconnect()
.Build();
// Register client-side handler
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Console.WriteLine($"{user}: {message}");
});
await connection.StartAsync();
// Send message to the Hub
await connection.InvokeAsync("SendMessage", "DotNetClient", "Hello from C#!");Step 4: Targeting Specific Audiences
SignalR Hubs provide granular control over message distribution
through the Clients API:
- All clients:
Clients.All.SendAsync(...) - Current caller only:
Clients.Caller.SendAsync(...) - Specific user:
Clients.User(userId).SendAsync(...)(requires authentication configured) - Specific groups:
Clients.Group("GroupName").SendAsync(...)
Clients can be dynamically added or removed from arbitrary groups:
public async Task JoinRoom(string roomName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName).SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} joined {roomName}.");
}Scaling SignalR
In multi-server deployments (such as load-balanced web farms),
clients connected to Server A cannot receive messages broadcast from
Server B by default. To resolve this, configure a backplane using Redis
(Microsoft.AspNetCore.SignalR.StackExchangeRedis) or the
Azure SignalR Service, allowing messages to be coordinated across all
active server instances seamlessly.