SignalR β Real-Time Web Apps Complete Guide
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
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:
Hubis the base class for all SignalR hubs β think of it as a controller for real-time communicationClients.All.SendAsyncbroadcasts a message to every connected clientClients.Group.SendAsyncsends only to clients in a specific groupContext.ConnectionIdis 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
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).Cross-Origin (CORS) issues: SignalR with WebSocket does not send CORS preflight requests, but your server still needs
AllowCredentials()for older transport fallbacks.Connection disposed after HTTP request: SignalR connections are long-lived. Do not
Disposethe hub or connection object while messages are flowing.Reconnection silent failure: If
withAutomaticReconnectis enabled, SignalR silently retries. Log the reconnection events to avoid silent data loss.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.
Large message handling: SignalR has a default message size limit (32 KB). For large payloads, increase
HubOptions.MaximumReceiveMessageSize.Hub method naming conflicts: Client methods are case-insensitive by default.
SendMessageandsendmessageconflict. Use[HubMethodName("...")]to disambiguate.
Practice Questions
- What transport does SignalR prefer, and what are the fallbacks?
- How do you send a message to a specific group of clients?
- What is the purpose of a backplane in SignalR?
- How do you authenticate users in a SignalR hub?
- What does
Context.ConnectionIdrepresent?
Answers:
- WebSocket is preferred; fallbacks are Server-Sent Events and Long Polling.
- Use
Clients.Group("groupName").SendAsync(...)after callingGroups.AddToGroupAsync. - A backplane shares connection state across multiple servers so any server can send to any client.
- Decorate the hub or its methods with
[Authorize]and pass a JWT token from the client. - 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.
Featured Snippet
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
Try It Yourself
What's Next
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro