SignalR is an ASP.NET Core library for real-time communication between server and clients.
A chat, a quote panel, or an online game need to receive changes without constantly asking the server.
With what we know so far (HTTP REST), you would have to make the Frontend ask every 2 seconds: “Any new messages? How about now? And now?”. This is called Polling, it’s inefficient and saturates your server.
The solution is to keep an open pipe between the Client and the Server. When something happens, the server instantly “pushes” the data to the client.
In .NET, the go-to library for this is SignalR.
SignalR abstracts the complexity of transports. Under the hood, it tries to use WebSockets (the modern standard). If the browser or proxy doesn’t support it, it automatically falls back to other technologies (Server-Sent Events or Long Polling) without you having to change a single line of code.
The Hub
In a normal API we have Controllers. In SignalR we have Hubs.
A Hub is a class that manages connections, groups, and message sending.
Create a Hubs folder and add a NotificacionesHub class:
using Microsoft.AspNetCore.SignalR;
public class NotificacionesHub : Hub
{
// This method is called by the client (Frontend) to send a message
public async Task EnviarMensaje(string usuario, string mensaje)
{
// "Clients.All" sends the message to ALL connected clients
await Clients.All.SendAsync("RecibirMensaje", usuario, mensaje);
}
// We can override connection events
public override async Task OnConnectedAsync()
{
await Clients.All.SendAsync("UsuarioConectado", Context.ConnectionId);
await base.OnConnectedAsync();
}
}Configuration in Program.cs
SignalR is already included in the ASP.NET Core framework (you don’t need to install extra NuGet packages for the server).
var builder = WebApplication.CreateBuilder(args);
// 1. Add the service
builder.Services.AddSignalR();
// ... CORS configuration if the client uses a different origin ...
builder.Services.AddCors(options =>
{
options.AddPolicy("ClientPermission", policy =>
{
policy.WithOrigins("http://localhost:5173") // Your Frontend
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // Required if using credentials/cookies
});
});
var app = builder.Build();
app.UseCors("ClientPermission");
// ... Middlewares ...
// 2. Map the Hub route (the endpoint)
app.MapHub<NotificacionesHub>("/hubs/notificaciones");
app.Run();Watch out for CORS!
If you use credentials with SignalR, configure specific origins with WithOrigins(...) and avoid combining credentials with AllowAnyOrigin(). This combination is not valid in CORS.
The Frontend Client
To connect from React, Angular, or Vanilla JS, we need the official client library.
npm install @microsoft/signalrLet’s look at a simple JavaScript example to connect and listen:
import { HubConnectionBuilder } from "@microsoft/signalr";
// 1. Build the connection
const connection = new HubConnectionBuilder()
.withUrl("http://localhost:5000/hubs/notificaciones")
.withAutomaticReconnect() // Retries if the internet goes down
.build();
// 2. Define what to do when the server talks to us
// "RecibirMensaje" must match the string used in C#
connection.on("RecibirMensaje", (usuario, mensaje) => {
console.log(`${usuario} says: ${mensaje}`);
});
// 3. Start the connection
async function start() {
try {
await connection.start();
console.log("Connected to SignalR!");
// We can send messages to the server
await connection.invoke("EnviarMensaje", "Luis", "Hello everyone!");
} catch (err) {
console.error(err);
}
}
start();Choosing Recipients
SignalR’s power lies in choosing who you talk to. We don’t always want a Broadcast to everyone.
Inside your Hub, you have the Clients property:
// To everyone (Broadcast)
await Clients.All.SendAsync("Event", data);
// Only to the caller (Reply)
await Clients.Caller.SendAsync("Event", "Received");
// To everyone EXCEPT the caller
await Clients.Others.SendAsync("Event", "Someone new has joined");
// To a specific user (Requires JWT Authentication)
// SignalR automatically looks for the "NameIdentifier" Claim (user ID)
await Clients.User("user-id-123").SendAsync("PrivateNotification", "Your order is ready");
// To a Group (Chat rooms)
await Groups.AddToGroupAsync(Context.ConnectionId, "Room_React");
await Clients.Group("Room_React").SendAsync("RoomMessage", "Hello Reacts!");Strongly Typed Hubs
Using text strings like .SendAsync("RecibirMensaje", ...) is dangerous. If you make a typo in the frontend, nothing works and there’s no error.
We can define an interface for the client:
public interface INotificacionesClient
{
Task RecibirMensaje(string usuario, string mensaje);
Task UsuarioConectado(string connectionId);
}And make our Hub inherit from Hub<T>:
// Now the Hub knows what methods the client has
public class NotificacionesHub : Hub<INotificacionesClient>
{
public async Task EnviarMensaje(string usuario, string mensaje)
{
// We now have intellisense and compile-time checking!
await Clients.All.RecibirMensaje(usuario, mensaje);
}
}SignalR maintains open connections, so memory, connections, and bandwidth need to be monitored. To scale across multiple instances, we can use a backplane like Redis or a managed service like Azure SignalR Service.