Skip to content

Aspnet Signalr

DodaTech 4 min read

title: ASP.NET Core SignalR — Complete Guide to Real-Time Communication description: 'Learn ASP.NET Core SignalR: WebSocket connections, hubs, real-time messaging, group management, scaling with Redis backplane, and building live dashboards.' date: 2026-06-28 lastmod: 2026-06-28 weight: 28 tags: [backend, aspnet]


ASP.NET Core SignalR enables real-time web functionality, pushing content from server to connected clients over WebSocket (with fallback transports) for live updates.

## What You'll Learn

By the end of this tutorial, you'll create SignalR hubs, send messages from server to clients, manage groups, build real-time dashboards, scale with Redis backplane, and handle connection events.

## Real-World Use

A stock trading platform uses SignalR to push price updates to hundreds of simultaneous users. Each user sees real-time price changes without refreshing the page.

## SignalR Learning Path

```mermaid
flowchart LR
  A[Minimal API] --> B[SignalR]
  B --> C[Testing]
  C --> D[Logging]
  D --> E[Health Checks]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

SignalR Hub

using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
    private readonly ILogger<ChatHub> _logger;
    public ChatHub(ILogger<ChatHub> logger) => _logger = logger;
    
    public async Task SendMessage(string user, string message)
    {
        _logger.LogInformation("Message from {User}: {Message}", user, message);
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
    public override async Task OnConnectedAsync()
    {
        _logger.LogInformation("Client connected: {ConnectionId}", Context.ConnectionId);
        await base.OnConnectedAsync();
    }
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        _logger.LogInformation("Client disconnected: {ConnectionId}", Context.ConnectionId);
        await base.OnDisconnectedAsync(exception);
    }
}

SignalR Configuration

// Program.cs
builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
});
builder.Services.AddCors(options => options.AddDefaultPolicy(policy =>
    policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().SetIsOriginAllowed(_ => true)));

var app = builder.Build();
app.UseCors();
app.MapHub<ChatHub>("/hubs/chat");
// MapHub<ChatHub>("/hubs/chat").RequireAuthorization(); // With auth

Client-Side JavaScript

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat")
    .withAutomaticReconnect([0, 2000, 5000, 10000])
    .build();

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messages").appendChild(li);
});

connection.start().then(() => {
    document.getElementById("sendBtn").addEventListener("click", () => {
        const user = document.getElementById("userInput").value;
        const msg = document.getElementById("msgInput").value;
        connection.invoke("SendMessage", user, msg);
    });
}).catch(err => console.error(err));

Groups

public class NotificationHub : Hub
{
    // Join a group (e.g., "user-123" for per-user notifications)
    public async Task JoinGroup(string groupName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
    }
    public async Task LeaveGroup(string groupName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
    }
}

// Server-side: send to a specific group
public class NotificationService
{
    private readonly IHubContext<NotificationHub> _hubContext;
    public NotificationService(IHubContext<NotificationHub> hub) => _hubContext = hub;
    
    public async Task NotifyUser(string userId, string message)
    {
        await _hubContext.Clients.Group($"user-{userId}")
            .SendAsync("ReceiveNotification", message);
    }
    public async Task BroadcastToAll(string message)
    {
        await _hubContext.Clients.All.SendAsync("ReceiveNotification", message);
    }
}

Scaling with Redis Backplane

dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
builder.Services.AddSignalR()
    .AddStackExchangeRedis("localhost:6379", options =>
    {
        options.Configuration.ChannelPrefix = "SignalR";
    });
// Multiple server instances share messages via Redis pub/sub

Common Mistakes

1. Not Handling Disconnections

SignalR clients disconnect unexpectedly. Implement OnDisconnectedAsync to clean up group memberships.

2. Ignoring Connection Security

Without authentication, anyone can connect. Use RequireAuthorization() on the hub endpoint.

3. Blocking in Hub Methods

Hub methods should be async. Blocking calls block all messages on that connection.

4. Not Configuring CORS

Browser-based SignalR clients need CORS configured with AllowCredentials().

5. Sending Too Much Data

Large messages (>32KB) may be rejected. Send only necessary data, not full objects.

Practice Questions

1. What transport does SignalR use by default?

WebSocket. Falls back to Server-Sent Events, then Long Polling if WebSocket isn't available.

2. How does SignalR handle reconnection?

Configure automatic reconnect with withAutomaticReconnect() on the client.

3. What is a SignalR group?

A named collection of connections. Messages sent to a group reach only members of that group.

4. How do you scale SignalR across multiple servers?

Use a Redis backplane (AddStackExchangeRedis). Servers share messages through Redis pub/sub.

5. Challenge: Create a hub that broadcasts real-time stock price updates to all connected clients.

public class StockHub : Hub { }
// Background service
public class StockService : BackgroundService
{
    private readonly IHubContext<StockHub> _hub;
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _hub.Clients.All.SendAsync("PriceUpdate", new { Symbol = "AAPL", Price = Random.Shared.NextDouble() * 100 });
            await Task.Delay(1000, stoppingToken);
        }
    }
}

FAQ

Can SignalR be used with non-browser clients?

Yes. .NET clients, Java clients, and Node.js clients can connect via the SignalR client library.

What is the difference between SendAsync and InvokeAsync?

SendAsync is fire-and-forget. InvokeAsync expects a return value from the client.

How do I secure a SignalR hub?

Use [Authorize] on the hub or RequireAuthorization() when mapping. Check Context.User for claims.

What happens when WebSocket isn't available?

SignalR negotiates the best available transport (SSE, then Long Polling).

Can I send files over SignalR?

Send file metadata and bytes as byte[], but SignalR isn't designed for large file transfers.

Mini Project: Real-Time Notification Hub

Build a SignalR notification system that pushes updates to specific users.

public class NotificationHub : Hub
{
    public async Task Register() => await Groups.AddToGroupAsync(Context.ConnectionId, Context.UserIdentifier);
}
app.MapHub<NotificationHub>("/hubs/notifications").RequireAuthorization();
// Notify specific user: _hubContext.Clients.User(userId).SendAsync("Notify", message);

What's Next

ASP.NET Core Testing ASP.NET Core Logging

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro