Skip to content

SignalR β€” Real-Time Web Apps Complete Guide

DodaTech Updated 2026-06-20 7 min read

In this tutorial, you'll learn about SignalR. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

SignalR is an ASP.NET library that enables real-time web functionality β€” server code can push content to connected clients instantly using WebSockets with automatic fallback to older transport protocols.

What You'll Learn

You'll understand how SignalR works under the hood, how to create hubs, send messages to individual clients or groups, scale across servers, and build a working real-time chat application.

Why SignalR Matters

Modern users expect real-time updates β€” live dashboards, instant notifications, collaborative editing. SignalR handles all the complexity of choosing the right transport (WebSocket, Server-Sent Events, Long Polling) automatically. DodaTech's Doda Browser uses SignalR for live sync features across devices.

Real-World Use

A stock trading platform pushes price updates to thousands of clients simultaneously. Without SignalR, each client would have to poll the server every few seconds β€” wasting bandwidth and slowing response times.

SignalR Learning Path

flowchart LR
  A["ASP.NET Core Basics"] --> B["Real-Time Concepts"]
  B --> C["SignalR Hubs & Clients"]
  C --> D["Groups & Users"]
  D --> E["Scaling & Azure SignalR"]
  E --> F["Production Real-Time Apps"]
  C:::current
  classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
ℹ️ Info

Prerequisites: Basic ASP.NET Core knowledge and familiarity with C#. You should understand HTTP request-response flow and JavaScript for client-side code.

How SignalR Works

SignalR uses the WebSocket protocol when available. If the client or server doesn't support WebSockets, it falls back to Server-Sent Events or Long Polling. You write one API β€” SignalR picks the best transport.

flowchart LR
  A["Browser Client"] -->|WebSocket| B["SignalR Hub"]
  A -->|Fallback: SSE| B
  A -->|Fallback: Long Polling| B
  B --> C["Broadcast"]
  B --> D["Group Send"]
  B --> E["Client Invoke"]

Transport Priority

Transport Bidirectional Low Latency Browser Support
WebSocket Yes Yes Modern browsers
Server-Sent Events Server→Client only Yes Modern browsers
Long Polling Yes No All browsers

Building a Chat Hub

Step 1: Create the SignalR Hub

using Microsoft.AspNetCore.SignalR;

namespace DodaChat.Hubs;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        // Broadcast to all connected clients
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    public async Task JoinGroup(string groupName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
        await Clients.Group(groupName).SendAsync(
            "ReceiveMessage", "System", $"{Context.ConnectionId} joined {groupName}"
        );
    }

    public async Task SendToGroup(string groupName, string user, string message)
    {
        await Clients.Group(groupName).SendAsync("ReceiveMessage", user, message);
    }
}

What's happening:

  • Hub is the base class for all SignalR hubs β€” think of it as a controller for real-time communication
  • Clients.All.SendAsync broadcasts a message to every connected client
  • Clients.Group.SendAsync sends only to clients in a specific group
  • Context.ConnectionId is a unique identifier for each connected client

Step 2: Register SignalR in Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();

var app = builder.Build();

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

app.Run();

Step 3: Client-Side JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>DodaChat</title>
  <style>
    #messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
    #messageInput { width: 70%; padding: 8px; }
    button { padding: 8px 16px; background: #0078d4; color: #fff; border: none; cursor: pointer; }
  </style>
</head>
<body>
  <h2>DodaChat β€” Real-Time Demo</h2>
  <div id="messages"></div>
  <input id="userInput" placeholder="Your name" />
  <input id="messageInput" placeholder="Type a message" />
  <button onclick="sendMessage()">Send</button>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
  <script>
    const connection = new signalR.HubConnectionBuilder()
      .withUrl("/chat")
      .build();

    connection.on("ReceiveMessage", (user, message) => {
      const msg = document.createElement("div");
      msg.innerHTML = `<strong>${user}:</strong> ${message}`;
      document.getElementById("messages").appendChild(msg);
    });

    connection.start().catch(err => console.error(err));

    function sendMessage() {
      const user = document.getElementById("userInput").value;
      const message = document.getElementById("messageInput").value;
      connection.invoke("SendMessage", user, message).catch(err => console.error(err));
    }
  </script>
</body>
</html>

Expected behavior: When you open two browser tabs and send a message from one, it appears instantly in the other.

Code Example: Live Dashboard Updates

public class DashboardHub : Hub
{
    private static int _activeUsers = 0;

    public override async Task OnConnectedAsync()
    {
        Interlocked.Increment(ref _activeUsers);
        await BroadcastStats();
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        Interlocked.Decrement(ref _activeUsers);
        await BroadcastStats();
        await base.OnDisconnectedAsync(exception);
    }

    private async Task BroadcastStats()
    {
        await Clients.All.SendAsync("UpdateStats", new
        {
            ActiveUsers = _activeUsers,
            Timestamp = DateTime.UtcNow
        });
    }
}

Expected output (in browser console):

{ activeUsers: 3, timestamp: "2026-06-20T14:30:00Z" }

Scaling SignalR Across Servers

By default, SignalR connections are tied to a single server. To scale across multiple servers (load-balanced), you need a backplane that shares connection state.

Backplane Pros Cons
Azure SignalR Service Managed, auto-scale, no maintenance Cost, vendor lock-in
Redis Fast, self-hosted option Requires Redis setup
RabbitMQ / Service Bus Reliable messaging Complex configuration

Azure SignalR Service Setup

builder.Services.AddSignalR()
    .AddAzureSignalR(Configuration["Azure:SignalR:ConnectionString"]);

Security: Authentication and Authorization

SignalR hubs can be secured with ASP.NET Core Identity or JWT tokens.

[Authorize]
public class SecureHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        var userName = Context.User?.Identity?.Name ?? "anonymous";
        await Clients.All.SendAsync("UserConnected", userName);
    }
}

For connection-level authentication via query string (used in mobile apps):

var connection = new HubConnectionBuilder()
    .WithUrl("/securechat", options =>
    {
        options.AccessTokenProvider = () => Task.FromResult(jwtToken);
    })
    .Build();

Common Errors

  1. WebSocket not supported on client: Old browsers or restrictive proxies block WebSocket. SignalR auto-falls back, but you can limit transports with .WithUrl(url, opts => opts.Transports = HttpTransportType.WebSockets).

  2. Cross-Origin (CORS) issues: SignalR with WebSocket does not send CORS preflight requests, but your server still needs AllowCredentials() for older transport fallbacks.

  3. Connection disposed after HTTP request: SignalR connections are long-lived. Do not Dispose the hub or connection object while messages are flowing.

  4. Reconnection silent failure: If withAutomaticReconnect is enabled, SignalR silently retries. Log the reconnection events to avoid silent data loss.

  5. Scalability β€” sticky sessions required without backplane: Without a backplane, all WebSocket connections from one client must go to the same server (sticky sessions). Use Azure SignalR Service or Redis backplane to avoid this.

  6. Large message handling: SignalR has a default message size limit (32 KB). For large payloads, increase HubOptions.MaximumReceiveMessageSize.

  7. Hub method naming conflicts: Client methods are case-insensitive by default. SendMessage and sendmessage conflict. Use [HubMethodName("...")] to disambiguate.

Practice Questions

  1. What transport does SignalR prefer, and what are the fallbacks?
  2. How do you send a message to a specific group of clients?
  3. What is the purpose of a backplane in SignalR?
  4. How do you authenticate users in a SignalR hub?
  5. What does Context.ConnectionId represent?

Answers:

  1. WebSocket is preferred; fallbacks are Server-Sent Events and Long Polling.
  2. Use Clients.Group("groupName").SendAsync(...) after calling Groups.AddToGroupAsync.
  3. A backplane shares connection state across multiple servers so any server can send to any client.
  4. Decorate the hub or its methods with [Authorize] and pass a JWT token from the client.
  5. It is a unique string identifier for each connected client, assigned by SignalR.

Challenge

Build a real-time collaborative whiteboard where each connected user can draw lines. When one user draws, the line appears on all other clients in real time. Use SignalR groups to implement room-based isolation.

Real-World Task

Create a live monitoring dashboard that receives system metrics (CPU, memory, disk) from a Windows service via SignalR. The dashboard should update every second and display the last 60 data points as a chart. Use the same pattern DodaTech uses in Durga Antivirus Pro for live threat monitoring.

What is SignalR?

SignalR is an ASP.NET Core library that enables real-time web communication by abstracting over WebSocket, Server-Sent Events, and Long Polling, allowing server code to push updates to connected clients instantly.

FAQ

Does SignalR work without WebSocket support?

Yes. SignalR automatically negotiates the best available transport. If WebSocket is unavailable, it falls back to Server-Sent Events (SSE), then Long Polling. Your application code stays the same regardless.

Can SignalR scale to thousands of concurrent users?

Yes, with a backplane. The Azure SignalR Service can handle millions of concurrent connections. Self-hosted SignalR with Redis backplane scales to tens of thousands per server.

How is SignalR different from regular AJAX polling?

AJAX polling sends HTTP requests at intervals regardless of whether data has changed. SignalR pushes data only when it changes, reducing bandwidth usage by 80–95% and providing instant updates.

Try It Yourself

▶ Try It YourselfEdit the code and click Run

What's Next

ASP.NET Core Web Development
Microsoft Azure Cloud
.NET MAUI β€” Cross-Platform UI Development

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro