aspnet-core-signalr-real-time-websockets

SignalR: comunicación en tiempo real con ASP.NET Core

  • 4 min

SignalR es una biblioteca de ASP.NET Core para comunicación en tiempo real entre servidor y clientes.

Un chat, un panel de cotizaciones o un juego online necesitan recibir cambios sin preguntar constantemente al servidor.

Con lo que sabemos hasta ahora (HTTP REST), tendrías que hacer que el Frontend preguntase cada 2 segundos: “¿Hay mensajes nuevos? ¿Y ahora? ¿Y ahora?”. Esto se llama Polling, es ineficiente y satura tu servidor.

La solución es mantener una tubería abierta entre el Cliente y el Servidor. Cuando pasa algo, el servidor “empuja” (Push) el dato al cliente al instante.

En .NET, la librería de referencia para esto es SignalR.

SignalR abstrae la complejidad de los transportes. Por debajo intenta usar WebSockets (el estándar moderno). Si el navegador o el proxy no lo soportan, hace fallback automático a otras tecnologías (Server-Sent Events o Long Polling) sin que tú tengas que cambiar ni una línea de código.

El hub

En una API normal tenemos Controllers. En SignalR tenemos Hubs.

Un Hub es una clase que gestiona las conexiones, los grupos y el envío de mensajes.

Crea una carpeta Hubs y añade una clase NotificacionesHub:

using Microsoft.AspNetCore.SignalR;

public class NotificacionesHub : Hub
{
    // Este método lo llama el cliente (Frontend) para enviar un mensaje
    public async Task EnviarMensaje(string usuario, string mensaje)
    {
        // "Clients.All" envía el mensaje a TODOS los conectados
        await Clients.All.SendAsync("RecibirMensaje", usuario, mensaje);
    }

    // Podemos sobrescribir eventos de conexión
    public override async Task OnConnectedAsync()
    {
        await Clients.All.SendAsync("UsuarioConectado", Context.ConnectionId);
        await base.OnConnectedAsync();
    }
}
Copied!

Configuración en Program.cs

SignalR ya viene incluido en el framework de ASP.NET Core (no necesitas instalar NuGets extra para el servidor).

var builder = WebApplication.CreateBuilder(args);

// 1. Añadir el servicio
builder.Services.AddSignalR();

// ... Configuración de CORS si el cliente usa otro origen ...
builder.Services.AddCors(options =>
{
    options.AddPolicy("ClientPermission", policy =>
    {
        policy.WithOrigins("http://localhost:5173") // Tu Frontend
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials(); // Necesario si usas credenciales/cookies
    });
});

var app = builder.Build();

app.UseCors("ClientPermission");

// ... Middlewares ...

// 2. Mapear la ruta del Hub (el endpoint)
app.MapHub<NotificacionesHub>("/hubs/notificaciones");

app.Run();
Copied!

¡Ojo con CORS! Si usas credenciales con SignalR, configura orígenes concretos con WithOrigins(...) y evita combinar credenciales con AllowAnyOrigin(). Esa combinación no es válida en CORS.

El cliente frontend

Para conectar desde React, Angular o Vanilla JS, necesitamos la librería cliente oficial.

npm install @microsoft/signalr
Copied!

Veamos un ejemplo simple en JavaScript para conectarse y escuchar:

import { HubConnectionBuilder } from "@microsoft/signalr";

// 1. Construir la conexión
const connection = new HubConnectionBuilder()
    .withUrl("http://localhost:5000/hubs/notificaciones")
    .withAutomaticReconnect() // Reintenta si se cae internet
    .build();

// 2. Definir qué hacer cuando el servidor nos hable
// "RecibirMensaje" debe coincidir con el string usado en el C#
connection.on("RecibirMensaje", (usuario, mensaje) => {
    console.log(`${usuario} dice: ${mensaje}`);
});

// 3. Arrancar la conexión
async function start() {
    try {
        await connection.start();
        console.log("Conectado a SignalR!");

        // Podemos enviar mensajes al servidor
        await connection.invoke("EnviarMensaje", "Luis", "Hola a todos!");
    } catch (err) {
        console.error(err);
    }
}

start();
Copied!

Elegir los destinatarios

La potencia de SignalR reside en elegir a quién le hablas. No siempre queremos hacer un “Broadcast” a todo el mundo.

Dentro de tu Hub, tienes la propiedad Clients:

// A todos (Broadcast)
await Clients.All.SendAsync("Evento", datos);

// Solo al que me ha llamado (Reply)
await Clients.Caller.SendAsync("Evento", "Recibido");

// A todos MENOS al que me ha llamado
await Clients.Others.SendAsync("Evento", "Ha entrado alguien nuevo");

// A un usuario específico (Requiere Autenticación JWT)
// SignalR busca automáticamente el Claim "NameIdentifier" (ID usuario)
await Clients.User("user-id-123").SendAsync("NotificacionPrivada", "Tu pedido está listo");

// A un Grupo (Salas de chat)
await Groups.AddToGroupAsync(Context.ConnectionId, "Sala_React");
await Clients.Group("Sala_React").SendAsync("MensajeSala", "Hola Reacts!");
Copied!

Hubs fuertemente tipados

Usar cadenas de texto como .SendAsync("RecibirMensaje", ...) es peligroso. Si te equivocas en una letra en el frontend, nada funciona y no hay error.

Podemos definir una interfaz para el cliente:

public interface INotificacionesClient
{
    Task RecibirMensaje(string usuario, string mensaje);
    Task UsuarioConectado(string connectionId);
}
Copied!

Y hacemos que nuestro Hub herede de Hub<T>:

// Ahora el Hub sabe qué métodos tiene el cliente
public class NotificacionesHub : Hub<INotificacionesClient>
{
    public async Task EnviarMensaje(string usuario, string mensaje)
    {
        // ¡Ya tenemos intellisense y chequeo de compilación!
        await Clients.All.RecibirMensaje(usuario, mensaje);
    }
}
Copied!

SignalR mantiene conexiones abiertas, así que hay que medir memoria, conexiones y ancho de banda. Para escalar entre varias instancias podemos usar un backplane como Redis o un servicio administrado como Azure SignalR Service.