Home / .NET / SignalR: Building Real-Time Web Applications with .NET
.NET

SignalR: Building Real-Time Web Applications with .NET

Build responsive real-time features—chat, notifications, dashboards—using SignalR with ASP.NET Core and scaling strategies.

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

SignalR: Building Real-Time Web Applications with .NET

Introduction

ASP.NET Core SignalR enables real-time, bidirectional communication between server and client, making it the go-to technology for live dashboards, chat applications, collaborative editing, notifications, and IoT data streaming in the .NET ecosystem. Unlike traditional HTTP request-response patterns, SignalR maintains persistent connections that allow the server to push updates to clients instantly.

This guide covers SignalR architecture, hub implementation, client integration, scaling with Azure SignalR Service, and production best practices.

Architecture Overview

Architecture Overview

Figure: Interactive dashboard – charts, lists, and global filter controls.

Architecture Overview: Load Balancer

Implementation

Implementation

Figure: Configuration and management dashboard with status overview.

Step 1: Configure SignalR Server

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add SignalR with Azure SignalR Service for scaling
builder.Services.AddSignalR()
    .AddAzureSignalR(); // Comment out for local development

// Add authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

app.MapHub<NotificationHub>("/hubs/notifications");
app.MapHub<ChatHub>("/hubs/chat");

app.Run();

Step 2: Create the Hub

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

[Authorize]
public class ChatHub : Hub
{
    private readonly ILogger<ChatHub> _logger;

    public ChatHub(ILogger<ChatHub> logger)
    {
        _logger = logger;
    }

    public override async Task OnConnectedAsync()
    {
        var userId = Context.UserIdentifier;
        _logger.LogInformation("User {UserId} connected", userId);

        // Add to user's groups
        await Groups.AddToGroupAsync(Context.ConnectionId, "general");
        await Clients.Group("general").SendAsync("UserJoined", userId);

        await base.OnConnectedAsync();
    }

    public async Task SendMessage(string roomId, string message)
    {
        var userId = Context.UserIdentifier;
        _logger.LogInformation("Message from {User} in {Room}", userId, roomId);

        await Clients.Group(roomId).SendAsync("ReceiveMessage", new
        {
            UserId = userId,
            Message = message,
            Timestamp = DateTime.UtcNow
        });
    }

    public async Task JoinRoom(string roomId)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomId);
        await Clients.Group(roomId).SendAsync("UserJoined",
            Context.UserIdentifier, roomId);
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        _logger.LogInformation("User {UserId} disconnected",
            Context.UserIdentifier);
        await base.OnDisconnectedAsync(exception);
    }
}

Step 3: JavaScript Client

import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/chat", {
        accessTokenFactory: () => getAccessToken()
    })
    .withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
    .configureLogging(signalR.LogLevel.Information)
    .build();

// Handle incoming messages
connection.on("ReceiveMessage", (message) => {
    console.log("New message:", message);
    appendMessageToUI(message);
});

connection.on("UserJoined", (userId, roomId) => {
    console.log(userId + " joined " + roomId);
});

// Connection lifecycle
connection.onreconnecting((error) => {
    console.warn("Reconnecting...", error);
    updateConnectionStatus("reconnecting");
});

connection.onreconnected((connectionId) => {
    console.log("Reconnected:", connectionId);
    updateConnectionStatus("connected");
});

connection.onclose((error) => {
    console.error("Connection closed:", error);
    updateConnectionStatus("disconnected");
});

// Start connection
async function startConnection() {
    try {
        await connection.start();
        console.log("Connected to SignalR hub");
        await connection.invoke("JoinRoom", "general");
    } catch (err) {
        console.error("Connection failed:", err);
        setTimeout(startConnection, 5000);
    }
}

// Send message
async function sendMessage(roomId, message) {
    await connection.invoke("SendMessage", roomId, message);
}

startConnection();

Scaling with Azure SignalR Service

Scaling with Azure SignalR Service

Figure: Program.cs – service registration with IntelliSense for DI lifetimes.

# Create Azure SignalR Service
az signalr create \
  --name mycompany-signalr \
  --resource-group rg-signalr-demo \
  --sku Standard_S1 \
  --unit-count 1 \
  --service-mode Default





# Get connection string
az signalr key list \
  --name mycompany-signalr \
  --resource-group rg-signalr-demo \
  --query primaryConnectionString \
  --output tsv

Best Practices

  1. Use Strongly-Typed Hubs: Define client interfaces for compile-time safety
  2. Implement Reconnection Logic: Always use withAutomaticReconnect on clients
  3. Group Management: Use groups for targeted messaging instead of broadcasting to all
  4. Keep Hub Methods Lightweight: Offload heavy processing to background services
  5. Monitor Connection Count: Set alerts for connection limits approaching tier thresholds
  6. Use MessagePack Protocol: Switch from JSON to MessagePack for 30-50% message size reduction

Architecture Decision and Tradeoffs

When designing application development solutions with .NET, consider these key architectural trade-offs:

Approach Best For Tradeoff
Managed / platform service Rapid delivery, reduced ops burden Less customisation, potential vendor lock-in
Custom / self-hosted Full control, advanced tuning Higher operational overhead and cost

Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.

Validation and Versioning

  • Last validated: April 2026
  • Validate examples against your tenant, region, and SKU constraints before production rollout.
  • Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.

Security and Governance Considerations

  • Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
  • Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
  • Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.

Cost and Performance Notes

  • Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
  • Baseline performance with synthetic and real-user checks before and after major changes.
  • Scale resources with measured thresholds and revisit sizing after usage pattern changes.

Official Microsoft References

  • https://learn.microsoft.com/dotnet/
  • https://learn.microsoft.com/aspnet/core/
  • https://learn.microsoft.com/azure/developer/dotnet/

Public Examples from Official Sources

  • These examples are sourced from official public Microsoft documentation and sample repositories.
  • Documentation examples: https://learn.microsoft.com/dotnet/
  • Sample repositories: https://github.com/dotnet/samples
  • Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.

Key Takeaways

  • ✅ SignalR abstracts WebSocket complexity while providing fallback transports
  • ✅ Azure SignalR Service handles scaling, connection management, and cross-server communication
  • ✅ Automatic reconnection with backoff ensures resilient client connections
  • ✅ Groups and user-targeted messaging enable efficient, scoped communication
  • ✅ Production deployments should use Azure SignalR Service for reliability at scale

Additional Resources

Discussion