# SignalR in ASP.NET Core 2026: Real-Time, Hubs and Interview Questions > Master SignalR for real-time web applications in ASP.NET Core. Complete guide covering hubs, groups, authentication, scaling with Redis, and common interview questions. - Published: 2026-08-12 - Updated: 2026-08-12 - Author: Anthony Fillion-Maillet - Tags: signalr, asp.net core, real-time, websockets, dotnet - Reading time: 12 min --- SignalR enables real-time bidirectional communication between servers and clients in ASP.NET Core applications. Unlike traditional HTTP request-response patterns, SignalR maintains persistent connections that allow servers to push updates to clients instantly—essential for chat applications, live dashboards, collaborative editing, and real-time notifications. > **SignalR Transport Fallback** > > SignalR automatically selects the best transport available: WebSockets first, then Server-Sent Events, then Long Polling. This fallback mechanism ensures compatibility across all browsers and network configurations without any code changes. ## How SignalR Hubs Enable Server-Client Communication A Hub acts as a high-level pipeline that handles method invocations between clients and servers. Clients call methods on the hub, and the hub can invoke methods on connected clients. This abstraction eliminates the complexity of managing WebSocket connections manually. The following example demonstrates a basic chat hub that broadcasts messages to all connected clients: ```csharp // ChatHub.cs using Microsoft.AspNetCore.SignalR; public class ChatHub : Hub { // Called when a client sends a message public async Task SendMessage(string user, string message) { // Broadcast to ALL connected clients await Clients.All.SendAsync("ReceiveMessage", user, message); } // Called automatically when a client connects public override async Task OnConnectedAsync() { await Clients.Caller.SendAsync("Connected", Context.ConnectionId); await base.OnConnectedAsync(); } // Called automatically when a client disconnects public override async Task OnDisconnectedAsync(Exception? exception) { // Clean up resources, notify other users, etc. await base.OnDisconnectedAsync(exception); } } ``` The `Clients` property provides access to all connected clients through various targeting options: `All`, `Caller`, `Others`, `Group`, and `User`. ## Configuring SignalR in ASP.NET Core 9 SignalR registration in ASP.NET Core 9 follows the standard middleware pattern. The configuration below enables JSON and MessagePack protocols with custom buffer sizes: ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); // Add SignalR services with configuration builder.Services.AddSignalR(options => { options.EnableDetailedErrors = builder.Environment.IsDevelopment(); options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB options.StreamBufferCapacity = 10; options.KeepAliveInterval = TimeSpan.FromSeconds(15); options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); }) .AddJsonProtocol(options => { options.PayloadSerializerOptions.PropertyNamingPolicy = null; // PascalCase }); var app = builder.Build(); // Map the hub endpoint app.MapHub("/chathub"); app.Run(); ``` The `KeepAliveInterval` and `ClientTimeoutInterval` settings control connection health monitoring. Clients that fail to respond within the timeout period are considered disconnected. ## Managing Groups for Targeted Message Delivery Groups provide a mechanism to organize connections and send messages to subsets of clients. A typical use case involves chat rooms, where users join specific rooms and only receive messages from those rooms. ```csharp // GroupChatHub.cs public class GroupChatHub : Hub { // Add the current connection to a group public async Task JoinRoom(string roomName) { await Groups.AddToGroupAsync(Context.ConnectionId, roomName); // Notify the group that someone joined await Clients.Group(roomName).SendAsync( "UserJoined", Context.User?.Identity?.Name ?? "Anonymous" ); } // Remove the current connection from a group public async Task LeaveRoom(string roomName) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName); await Clients.Group(roomName).SendAsync( "UserLeft", Context.User?.Identity?.Name ?? "Anonymous" ); } // Send a message to a specific group only public async Task SendToRoom(string roomName, string message) { await Clients.Group(roomName).SendAsync( "ReceiveMessage", Context.User?.Identity?.Name, message ); } } ``` Group membership is tied to connections, not users. When a user reconnects, they must rejoin their groups. For persistent group membership, store group associations in a database and rejoin automatically in `OnConnectedAsync`. > **Group Persistence** > > Groups exist only in memory and are not persisted across server restarts or in scaled-out scenarios without a backplane. For production deployments with multiple servers, use Redis or Azure SignalR Service as a backplane. ## Securing SignalR Connections with Authentication SignalR integrates with ASP.NET Core authentication. The `[Authorize]` attribute restricts hub access to authenticated users, and `Context.User` provides access to the authenticated user's claims. ```csharp // SecureHub.cs using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; [Authorize] // Require authentication for the entire hub public class SecureHub : Hub { // Access user claims through Context.User public async Task SendPrivateMessage(string recipientUserId, string message) { var senderName = Context.User?.Identity?.Name ?? throw new HubException("User not authenticated"); // Send to a specific user (all their connections) await Clients.User(recipientUserId).SendAsync( "PrivateMessage", senderName, message ); } [Authorize(Roles = "Admin")] // Role-based authorization on method public async Task BroadcastAnnouncement(string announcement) { await Clients.All.SendAsync("Announcement", announcement); } } ``` For WebSocket connections, authentication tokens must be passed via query string since WebSockets do not support custom headers. Configure the JavaScript client to include the token: ```typescript // signalr-client.ts import * as signalR from "@microsoft/signalr"; const connection = new signalR.HubConnectionBuilder() .withUrl("/securehub", { accessTokenFactory: () => localStorage.getItem("authToken") || "" }) .withAutomaticReconnect() .build(); await connection.start(); ``` On the server, configure JWT authentication to read the token from the query string: ```csharp // Program.cs - JWT configuration for SignalR builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Events = new JwtBearerEvents { OnMessageReceived = context => { // Read token from query string for SignalR var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/securehub")) { context.Token = accessToken; } return Task.CompletedTask; } }; }); ``` ## Scaling SignalR with Redis Backplane In a load-balanced environment, clients may connect to different server instances. Without a backplane, messages sent from one server only reach clients connected to that server. [Redis](https://redis.io/docs/) serves as a backplane to synchronize messages across all server instances. ```csharp // Program.cs - Redis backplane configuration builder.Services.AddSignalR() .AddStackExchangeRedis(options => { options.Configuration = builder.Configuration .GetConnectionString("Redis"); // Optional: configure Redis-specific options options.Configuration.ChannelPrefix = RedisChannel.Literal("MyApp_SignalR_"); }); ``` The Redis backplane publishes all SignalR messages to Redis channels. Each server instance subscribes to these channels and delivers messages to its local clients. This approach adds minimal latency (typically 1-2ms) while enabling horizontal scaling. For enterprise deployments, [Azure SignalR Service](https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-overview) provides a managed alternative that handles scaling, connection management, and availability automatically. ## Strongly-Typed Hubs for Compile-Time Safety Strongly-typed hubs replace magic strings with interface methods, enabling compile-time verification of client method calls: ```csharp // INotificationClient.cs public interface INotificationClient { Task ReceiveNotification(string title, string message, string severity); Task UpdateProgress(int percentage); Task TaskCompleted(Guid taskId, bool success); } // NotificationHub.cs public class NotificationHub : Hub { public async Task NotifyAll(string title, string message) { // Compile-time checking - no magic strings await Clients.All.ReceiveNotification(title, message, "info"); } public async Task ReportProgress(string groupName, int percentage) { // IDE autocomplete works here await Clients.Group(groupName).UpdateProgress(percentage); } } ``` Refactoring becomes safer because renaming a method in the interface immediately shows all call sites that need updates. ## Streaming Data with SignalR Channels SignalR supports streaming for scenarios where data is produced incrementally, such as progress updates, log tailing, or real-time sensor data. Both server-to-client and client-to-server streaming are supported. ```csharp // StreamingHub.cs public class StreamingHub : Hub { // Server-to-client streaming with IAsyncEnumerable public async IAsyncEnumerable StreamStockPrices( string[] symbols, [EnumeratorCancellation] CancellationToken cancellationToken) { var random = new Random(); while (!cancellationToken.IsCancellationRequested) { foreach (var symbol in symbols) { yield return new StockPrice { Symbol = symbol, Price = random.NextDouble() * 1000, Timestamp = DateTime.UtcNow }; } await Task.Delay(1000, cancellationToken); } } // Client-to-server streaming public async Task UploadStream(IAsyncEnumerable stream) { await foreach (var entry in stream) { // Process each log entry as it arrives await ProcessLogEntry(entry); } } } public record StockPrice(string Symbol, double Price, DateTime Timestamp); public record LogEntry(string Level, string Message, DateTime Timestamp); ``` The JavaScript client consumes the stream using an async iterator: ```typescript // streaming-client.ts const stream = connection.stream("StreamStockPrices", ["AAPL", "MSFT"]); stream.subscribe({ next: (price) => console.log(`${price.symbol}: $${price.price}`), error: (err) => console.error(err), complete: () => console.log("Stream completed") }); ``` ## Common SignalR Interview Questions Technical interviews frequently cover SignalR architecture and real-world scenarios. The following questions appear regularly in [.NET interview sessions](/technologies/dotnet/interview-questions/web-api-development): > **Interview Tip** > > Explain the transport negotiation process: SignalR first attempts WebSockets, falls back to Server-Sent Events, then Long Polling. Understand when each transport is used and its limitations. **Q: How does SignalR handle connection state when a client temporarily loses connectivity?** SignalR maintains connection state on the server for a configurable period. With automatic reconnection enabled, the client attempts to reconnect using the same connection ID. If successful, the connection resumes without losing group memberships. If the disconnect exceeds the timeout, a new connection is established and the client must rejoin groups. **Q: What is the difference between `Clients.User()` and `Clients.Client()`?** `Clients.User(userId)` targets all connections belonging to a specific authenticated user—useful when one user has multiple browser tabs open. `Clients.Client(connectionId)` targets a single specific connection. User-based targeting requires authentication and uses `IUserIdProvider` to map connections to user IDs. **Q: How would you implement presence detection (showing who is online)?** Track connections in `OnConnectedAsync` and `OnDisconnectedAsync`. Store user-to-connection mappings in a concurrent dictionary or distributed cache. For scaled-out deployments, use a shared store like Redis. Broadcast presence updates to relevant groups when users connect or disconnect. **Q: Explain the purpose of a SignalR backplane.** A backplane synchronizes messages across multiple server instances in a load-balanced environment. Without it, a message sent from server A only reaches clients connected to server A. Redis and Azure SignalR Service are common backplane options. The backplane publishes messages to a shared channel that all servers subscribe to. ## Performance Optimization Strategies SignalR performance depends on message size, frequency, and connection count. The [ASP.NET Core performance best practices](https://learn.microsoft.com/en-us/aspnet/core/signalr/performance) documentation provides detailed benchmarks. - **Use MessagePack protocol** for binary serialization—reduces payload size by 30-50% compared to JSON - **Batch messages** when possible instead of sending many small messages - **Limit group sizes** to thousands of members; for larger audiences, consider pub/sub patterns - **Enable compression** for text-heavy payloads: `.AddJsonProtocol().AddHubOptions(o => o.EnableDetailedErrors = false)` - **Set appropriate timeouts** to release resources from stale connections promptly ```csharp // MessagePack configuration for smaller payloads builder.Services.AddSignalR() .AddMessagePackProtocol(options => { options.SerializerOptions = MessagePackSerializerOptions.Standard .WithCompression(MessagePackCompression.Lz4Block); }); ``` ## Conclusion - SignalR abstracts transport selection (WebSockets, SSE, Long Polling) and provides a consistent programming model for real-time communication - Hubs centralize connection management; strongly-typed hubs add compile-time safety for client method calls - Groups enable targeted message delivery to subsets of connections—rejoin groups after reconnection - Authentication integrates with ASP.NET Core identity; pass tokens via query string for WebSocket connections - Scale horizontally with Redis backplane or Azure SignalR Service to synchronize messages across server instances - Streaming supports incremental data delivery with `IAsyncEnumerable` for both server-to-client and client-to-server scenarios --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/dotnet/signalr-aspnet-core-real-time-hubs-guide