Adding Real-Time Functionality to .NET Apps with SignalR
Learn ASP.NET Core SignalR step by step: hubs, clients, groups, and scaling with Redis or Azure, explained for absolute beginners.
Imagine you are waiting for an auto-rickshaw using a ride app. The little car icon moves on the map by itself. You did not press refresh. The screen just updates on its own. That magic feeling, where the server tells your phone "here is new info, right now", is called real-time communication.
Most websites do not work like that. Normally your browser asks the server for something, and the server answers. Like sending a letter and waiting for a reply. But for a live cricket score, a chat message, or that moving auto on the map, asking again and again is slow and wasteful.
SignalR is the part of ASP.NET Core that gives your .NET apps this real-time power. In this guide we will learn what it is, how it works, and how to build a tiny live chat. We will keep the words simple and the steps small.
What problem does SignalR solve?
Think of a classroom. The old way of getting updates is like a student raising their hand every ten seconds to ask "Sir, any homework yet? Any homework yet?" That is tiring and noisy. This old trick is called polling.
SignalR flips it around. Now the teacher (the server) can simply call out "Here is your homework!" the moment it is ready. Every student (the browser) hears it instantly. The connection stays open like an always-on phone line.
Here is a quick comparison so you can see the difference clearly.
| Way of working | How it behaves | Good for |
|---|---|---|
| Normal request/response | Browser asks, server replies once | Loading a page, saving a form |
| Polling | Browser keeps asking on a timer | Simple but wasteful updates |
| SignalR (real-time) | Server pushes the moment data is ready | Chat, live scores, dashboards |
Where is SignalR used in real life?
You have already used apps built on ideas like SignalR. Some everyday examples:
- Chat apps where a message appears the second your friend sends it.
- Live cricket or football scores that tick up on their own.
- Online voting or polls where the bar chart grows live.
- Dashboards in offices showing sales or server health, refreshing by themselves.
- Notifications, like "your order has been packed", popping up without a refresh.
SignalR is great any time the server knows something before the user does, and you want the user to see it right away.
The two key parts: Hub and Client
SignalR has two friends that talk to each other.
- The Hub lives on the server. Think of it as a telephone exchange operator. Clients call methods on the hub, and the hub can call methods back on the clients.
- The Client lives in the browser (or another app). It connects to the hub and listens for calls.
The beautiful part is that they can call each other's methods almost like they are in the same room, even though one is on a server far away and the other is on your phone.
How a message travels
Steps
Type
User writes a message
Send to Hub
Client calls SendMessage
Hub broadcasts
Hub calls all clients
Others receive
Browsers show the text
How does SignalR pick a transport?
A transport is just the road the messages travel on. SignalR is polite and clever. It tries the fastest road first, and if that road is blocked, it quietly takes a slower one. You do not have to change your code at all.
The roads, from fastest to slowest, are:
| Transport | What it is | Speed |
|---|---|---|
| WebSockets | A two-way always-open pipe | Fastest, preferred |
| Server-Sent Events | Server keeps pushing down one pipe | Medium |
| Long Polling | Browser asks and waits patiently | Slowest fallback |
This automatic fallback is one reason SignalR is loved. It just works, even on tricky networks or strict company firewalls.
Let us build a tiny chat
Now the fun part. We will make a very small chat hub. Do not worry if some words are new. Read it slowly, like a recipe.
First, we create the Hub on the server. A hub is just a C# class that inherits from Hub. We add a method that clients can call.
using Microsoft.AspNetCore.SignalR;
public class ChatHub : Hub
{
// Clients call this method to send a message.
public async Task SendMessage(string user, string message)
{
// The hub then calls "ReceiveMessage" on every connected client.
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}Read that again. The client calls SendMessage. Inside, the server turns around and shouts ReceiveMessage to everybody. That is the heart of real-time.
Next we wire it up in Program.cs. We tell ASP.NET Core "please add SignalR, and put my ChatHub at this address".
var builder = WebApplication.CreateBuilder(args);
// 1. Add SignalR to the app's services.
builder.Services.AddSignalR();
var app = builder.Build();
app.UseStaticFiles();
// 2. Give the hub a web address clients can reach.
app.MapHub<ChatHub>("/chathub");
app.Run();Now the browser side. SignalR has a small JavaScript library. The client connects, listens for ReceiveMessage, and can call SendMessage. Here is the important piece in plain JavaScript.
// (JavaScript on the web page)
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.build();
// Listen for messages pushed by the server.
connection.on("ReceiveMessage", (user, message) => {
const li = document.createElement("li");
li.textContent = user + " says: " + message;
document.getElementById("messages").appendChild(li);
});
// Start the connection, then allow sending.
connection.start().then(() => {
document.getElementById("sendButton").addEventListener("click", () => {
const user = document.getElementById("userInput").value;
const text = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, text);
});
});That is a full working chat. When one person clicks send, everyone watching the page sees the message appear at the same moment. No refresh button anywhere.
Setup checklist
Steps
Add SignalR
AddSignalR in services
Make a Hub
Class inheriting Hub
Map the Hub
MapHub with a URL
Connect client
Browser starts connection
Sending to the right people: groups and users
Shouting to everyone with Clients.All is fine for a single big room. But often you want to message only some people. SignalR gives you neat ways to do this.
Clients.Allsends to everyone connected.Clients.Callersends back to only the person who called.Clients.Otherssends to everyone except the caller.Clients.Group("Class10A")sends to a named group.Clients.User(userId)sends to all devices of one logged-in user.
A group is just a named bucket of connections. Think of it like a WhatsApp group. You add people in, you message the group, and only its members hear it. Here is how you add someone to a group and message it.
public class ChatHub : Hub
{
public async Task JoinRoom(string roomName)
{
// Put this connection into a named group.
await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
await Clients.Group(roomName).SendAsync("Notify", "A new friend joined " + roomName);
}
public async Task SendToRoom(string roomName, string message)
{
// Only people in this room will hear it.
await Clients.Group(roomName).SendAsync("ReceiveMessage", message);
}
}Each browser tab gets a unique connection ID. One logged-in person can have several connection IDs (phone, laptop, tablet). SignalR can target either, which is very handy.
What happens when there are many servers?
This is the most important grown-up topic, so let us go slowly with our auto-rickshaw idea again.
Imagine your chat app becomes very popular. One server cannot handle all the users, so you run three servers behind a load balancer. Now there is a problem. Asha is connected to Server 1. Bilal is connected to Server 2. When Asha sends a message, Server 1 only knows about its own users. It cannot reach Bilal on Server 2. The message gets stuck.
The fix is called a backplane. A backplane is a shared notice board that all servers can read and write. When Server 1 gets a message, it pins it on the shared board. Servers 2 and 3 see it and deliver it to their own users. Now everyone hears everything, no matter which server they landed on.
There are two popular ways to build this notice board.
| Option | Best when | Notes |
|---|---|---|
| Redis backplane | You host your own servers | Keep Redis in the same data center to avoid lag |
| Azure SignalR Service | You run on Azure cloud | Microsoft manages scaling; no sticky sessions needed |
A quick word on sticky sessions
When you use the Redis backplane, you must turn on sticky sessions. This means once a user connects to Server 2, the load balancer must keep sending that same user back to Server 2. If it bounces them around, the connection breaks.
With Azure SignalR Service you do not need sticky sessions, because clients are quietly redirected to the Azure service the moment they connect. The service holds all the connections so your own servers stay light and simple. If your app already lives in Azure, this is usually the easiest path.
Setting up a Redis backplane is only a few lines. You add a small package and chain one call.
builder.Services.AddSignalR()
.AddStackExchangeRedis("localhost:6379", options =>
{
// A channel prefix keeps your app separate from other apps
// sharing the same Redis server. Always set one in production.
options.Configuration.ChannelPrefix =
StackExchange.Redis.RedisChannel.Literal("MyChatApp");
});That channel prefix is important. Without it, two different apps sharing one Redis could accidentally hear each other's messages. The prefix is like writing your name on your lunchbox so nobody mixes it up.
One honest warning from the docs: if the Redis server goes down, SignalR does not save messages to send later. Any messages during the outage are simply lost. So treat your backplane as something that needs to stay healthy.
A peek at what is new
SignalR keeps improving with each .NET release. On modern .NET 10, you also get:
- AOT and trimming support, so apps can start faster and ship smaller in supported scenarios.
- Polymorphic hub methods, meaning a hub method can accept a base type and correctly handle different child types.
- Distributed tracing with
ActivitySource, so you can follow a single message across servers when debugging. This is a real gift when something goes wrong in production.
You do not need these on day one. But it is nice to know your simple chat app sits on top of a library that big companies trust at huge scale.
Common beginner mistakes to avoid
A few gentle tips to save you headaches:
- Do not store user data in the hub class as a field. A new hub instance is created for each call. Use groups, a database, or a shared service instead.
- Always handle the connection closing. Networks drop. Use the client's reconnect features so users come back smoothly.
- Keep hub methods small and fast. Long, heavy work blocks the pipe. Hand big jobs to a background service.
- Never trust input blindly. Just like any web endpoint, check and clean what clients send.
Quick recap
- SignalR lets your .NET server push updates to clients instantly, instead of clients asking again and again.
- A Hub is a server class where clients and server call each other's methods.
- The client library (JavaScript or .NET) connects to the hub and listens for messages.
- SignalR picks the best transport automatically: WebSockets first, then SSE, then long polling.
- You can target all clients, the caller, groups, or a single user.
- To run on many servers, you need a backplane: Redis (self-hosted, needs sticky sessions) or Azure SignalR Service (managed, no sticky sessions).
- Always set a channel prefix for Redis, and remember messages are lost if the backplane goes down.
Start small. Build the chat above, watch two browser tabs talk to each other live, and feel that same magic as the moving auto on the map. Once that clicks, every real-time feature becomes easy.
References and further reading
- Overview of ASP.NET Core SignalR — Microsoft Learn
- Use hubs in ASP.NET Core SignalR — Microsoft Learn
- Get started with ASP.NET Core SignalR (tutorial) — Microsoft Learn
- Redis backplane for SignalR scale-out — Microsoft Learn
- SignalR production hosting and scaling — Microsoft Learn
- Scaling SignalR with a Redis backplane — Milan Jovanović
Related Posts
Scaling SignalR With a Redis Backplane in ASP.NET Core
Learn how a Redis backplane lets your ASP.NET Core SignalR app run on many servers so every connected user still gets every real-time message.
How to Add JWT Authentication to SignalR Hubs in ASP.NET Core
A beginner-friendly guide to securing SignalR hubs with JWT tokens in ASP.NET Core, including the access_token query string trick and the [Authorize] attribute.
Scheduling Background Jobs with Quartz.NET in ASP.NET Core
Learn Quartz.NET step by step in ASP.NET Core: jobs, triggers, cron schedules, dependency injection, and database persistence, explained for beginners.
Real-Time Server-Sent Events in ASP.NET Core and .NET 10
Learn Server-Sent Events (SSE) in ASP.NET Core and .NET 10 with the new TypedResults.ServerSentEvents API, explained simply for beginners.
TickerQ: The Modern .NET Job Scheduler That Beats Quartz and Hangfire
Learn TickerQ, the fast, reflection-free .NET job scheduler with cron and time jobs, EF Core storage, retries, and a live dashboard, explained for beginners.
Improving ASP.NET Core Dependency Injection with Scrutor
Learn how Scrutor makes ASP.NET Core dependency injection easier with assembly scanning and decoration, explained in simple, beginner-friendly steps.