SignalR
|
This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14. This section’s bibliography lists the reference material consulted while preparing these pages. |
SignalR pushes messages from the server to connected clients in real time, over whichever transport the client and server negotiate, and falls back automatically when a better one is unavailable.
Hubs
A hub is the server-side endpoint clients connect to and call methods on. Hub<T> makes client calls
strongly typed — the compiler catches a wrong method name or argument type instead of failing at runtime:
public interface IChatClient
{
Task ReceiveMessage(string user, string text);
}
public sealed class ChatHub : Hub<IChatClient>
{
public async Task SendMessage(string user, string text)
=> await Clients.All.ReceiveMessage(user, text);
public async Task JoinRoom(string room, string user)
{
await Groups.AddToGroupAsync(Context.ConnectionId, room);
await Clients.Group(room).ReceiveMessage("system", $"{user} joined");
}
public override async Task OnConnectedAsync()
{
await Clients.Caller.ReceiveMessage("system", "welcome");
await base.OnConnectedAsync();
}
}
// Program.cs
builder.Services.AddSignalR();
app.MapHub<ChatHub>("/hubs/chat");
Clients and groups
| Target | Sends to |
|---|---|
|
Every connected client. |
|
Only the connection that invoked the current hub method. |
|
Every connection except the caller. |
|
Members of a named group. |
|
All connections (e.g. multiple tabs/devices) for one user id. |
|
One specific connection. |
Groups.AddToGroupAsync / RemoveFromGroupAsync manage group membership; groups are not persisted, so a
reconnecting client must rejoin (typically from OnConnectedAsync).
Transports
SignalR negotiates the best available transport and falls back automatically:
-
WebSockets — full-duplex, lowest overhead (preferred)
-
Server-Sent Events — a server-to-client stream plus separate POSTs for client-to-server calls
-
Long polling — last resort
MapHub<ChatHub>("/hubs/chat") exposes the negotiate endpoint plus each transport. The underlying browser
mechanisms are covered in Networking.
Clients
import * as signalR from "@microsoft/signalr";
const conn = new signalR.HubConnectionBuilder()
.withUrl("/hubs/chat")
.withAutomaticReconnect()
.build();
conn.on("ReceiveMessage", (user, text) => appendLine(user, text));
await conn.start();
await conn.invoke("SendMessage", "alice", "hello");
var conn = new HubConnectionBuilder()
.WithUrl("https://example.com/hubs/chat")
.WithAutomaticReconnect()
.Build();
conn.On<string, string>("ReceiveMessage", (user, text) => Console.WriteLine($"{user}: {text}"));
await conn.StartAsync();
Both a JavaScript/TypeScript client (@microsoft/signalr) and a .NET client (Microsoft.AspNetCore.SignalR.Client)
ship officially; Java and other community clients also exist. See
Use the JavaScript client and
Use the .NET client.
Streaming
Return or accept IAsyncEnumerable<T> / ChannelReader<T> for long-running data flows in either direction:
public async IAsyncEnumerable<int> CounterAsync(
int count, [EnumeratorCancellation] CancellationToken ct)
{
for (var i = 0; i < count; i++)
{
await Task.Delay(500, ct);
yield return i;
}
}
Authentication
[Authorize] works on a hub class or an individual hub method, same as an MVC controller; the access token
rides the connection (typically as a query-string parameter on the negotiate/WebSocket handshake, since custom
headers aren’t available to browser WebSocket APIs):
[Authorize]
public sealed class ChatHub : Hub<IChatClient>
{
[Authorize(Policy = "AdminOnly")]
public Task Broadcast(string text) => Clients.All.ReceiveMessage("admin", text);
}
See Authorization for policies, and Authentication and authorization in ASP.NET Core SignalR.
Scale-out
A single server holds every open connection in memory; running more than one server needs a backplane so a message sent on one server reaches clients connected to another:
// dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
builder.Services.AddSignalR()
.AddStackExchangeRedis("localhost:6379", o => o.Configuration.ChannelPrefix = "chat");
The fully managed Azure SignalR Service is the alternative: it offloads connection management entirely, so app servers don’t hold WebSocket connections at all. See Host and scale ASP.NET Core SignalR.
Beyond the hub
-
IHubContext<ChatHub, IChatClient>— inject it anywhere (a controller, a background service) to push messages without being inside a hub method. -
MessagePack—AddSignalR().AddMessagePackProtocol()swaps the default JSON protocol for a compact binary format. -
Hub filters —
IHubFilterwraps every hub invocation for cross-cutting logic (logging, validation), the SignalR analog of MVC filters.