SignalR en ASP.NET Core 2026: Tiempo Real, Hubs y Preguntas de Entrevista

Guía completa de SignalR para aplicaciones web en tiempo real con ASP.NET Core. Hubs, grupos, autenticación, escalamiento con Redis y preguntas de entrevista técnica.

Guía visual de SignalR ASP.NET Core

SignalR habilita la comunicación bidireccional en tiempo real entre servidores y clientes en aplicaciones ASP.NET Core. A diferencia de los patrones tradicionales de solicitud-respuesta HTTP, SignalR mantiene conexiones persistentes que permiten a los servidores enviar actualizaciones a los clientes instantáneamente—esencial para aplicaciones de chat, dashboards en vivo, edición colaborativa y notificaciones en tiempo real.

Fallback de Transporte SignalR

SignalR selecciona automáticamente el mejor transporte disponible: primero WebSockets, luego Server-Sent Events, después Long Polling. Este mecanismo de respaldo garantiza compatibilidad con todos los navegadores y configuraciones de red sin necesidad de cambios en el código.

Cómo los Hubs de SignalR Habilitan la Comunicación Servidor-Cliente

Un Hub actúa como un pipeline de alto nivel que maneja las invocaciones de métodos entre clientes y servidores. Los clientes llaman métodos en el hub, y el hub puede invocar métodos en los clientes conectados. Esta abstracción elimina la complejidad de gestionar conexiones WebSocket manualmente.

El siguiente ejemplo demuestra un hub de chat básico que transmite mensajes a todos los clientes conectados:

ChatHub.cscsharp
using Microsoft.AspNetCore.SignalR;

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

    // Called automatically when a client connects
    public override async Task OnConnectedAsync()
    {
        await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    // Called automatically when a client disconnects
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // Clean up resources, notify other users, etc.
        await base.OnDisconnectedAsync(exception);
    }
}

La propiedad Clients proporciona acceso a todos los clientes conectados a través de varias opciones de direccionamiento: All, Caller, Others, Group y User.

Configuración de SignalR en ASP.NET Core 9

El registro de SignalR en ASP.NET Core 9 sigue el patrón estándar de middleware. La configuración a continuación habilita los protocolos JSON y MessagePack con tamaños de buffer personalizados:

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

// Add SignalR services with configuration
builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
    options.StreamBufferCapacity = 10;
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
})
.AddJsonProtocol(options =>
{
    options.PayloadSerializerOptions.PropertyNamingPolicy = null; // PascalCase
});

var app = builder.Build();

// Map the hub endpoint
app.MapHub<ChatHub>("/chathub");

app.Run();

Las configuraciones KeepAliveInterval y ClientTimeoutInterval controlan el monitoreo del estado de las conexiones. Los clientes que no respondan dentro del período de timeout se consideran desconectados.

Gestión de Grupos para Entrega Dirigida de Mensajes

Los grupos proporcionan un mecanismo para organizar conexiones y enviar mensajes a subconjuntos de clientes. Un caso de uso típico involucra salas de chat, donde los usuarios se unen a salas específicas y solo reciben mensajes de esas salas.

GroupChatHub.cscsharp
public class GroupChatHub : Hub
{
    // Add the current connection to a group
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        
        // Notify the group that someone joined
        await Clients.Group(roomName).SendAsync(
            "UserJoined", 
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Remove the current connection from a group
    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Send a message to a specific group only
    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync(
            "ReceiveMessage",
            Context.User?.Identity?.Name,
            message
        );
    }
}

La membresía de grupo está vinculada a las conexiones, no a los usuarios. Cuando un usuario se reconecta, debe volver a unirse a sus grupos. Para membresía de grupo persistente, las asociaciones de grupos deben almacenarse en una base de datos y unirse automáticamente en OnConnectedAsync.

Persistencia de Grupos

Los grupos solo existen en memoria y no se persisten entre reinicios del servidor o en escenarios de escalamiento sin un backplane. Para implementaciones en producción con múltiples servidores, se debe usar Redis o Azure SignalR Service como backplane.

Asegurando Conexiones SignalR con Autenticación

SignalR se integra con la autenticación de ASP.NET Core. El atributo [Authorize] restringe el acceso al hub a usuarios autenticados, y Context.User proporciona acceso a los claims del usuario autenticado.

SecureHub.cscsharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

[Authorize] // Require authentication for the entire hub
public class SecureHub : Hub
{
    // Access user claims through Context.User
    public async Task SendPrivateMessage(string recipientUserId, string message)
    {
        var senderName = Context.User?.Identity?.Name 
            ?? throw new HubException("User not authenticated");
        
        // Send to a specific user (all their connections)
        await Clients.User(recipientUserId).SendAsync(
            "PrivateMessage",
            senderName,
            message
        );
    }

    [Authorize(Roles = "Admin")] // Role-based authorization on method
    public async Task BroadcastAnnouncement(string announcement)
    {
        await Clients.All.SendAsync("Announcement", announcement);
    }
}

Para conexiones WebSocket, los tokens de autenticación deben pasarse mediante query string ya que los WebSockets no soportan headers personalizados. La configuración del cliente JavaScript para incluir el token es la siguiente:

signalr-client.tstypescript
import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/securehub", {
        accessTokenFactory: () => localStorage.getItem("authToken") || ""
    })
    .withAutomaticReconnect()
    .build();

await connection.start();

En el servidor, la autenticación JWT debe configurarse para leer el token desde el query string:

Program.cs - JWT configuration for SignalRcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Read token from query string for SignalR
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) && 
                    path.StartsWithSegments("/securehub"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });

¿Listo para aprobar tus entrevistas de .NET?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Escalando SignalR con Backplane Redis

En un entorno con balanceo de carga, los clientes pueden conectarse a diferentes instancias de servidor. Sin un backplane, los mensajes enviados desde un servidor solo llegan a los clientes conectados a ese servidor. Redis sirve como backplane para sincronizar mensajes entre todas las instancias del servidor.

Program.cs - Redis backplane configurationcsharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(options =>
    {
        options.Configuration = builder.Configuration
            .GetConnectionString("Redis");
        
        // Optional: configure Redis-specific options
        options.Configuration.ChannelPrefix = 
            RedisChannel.Literal("MyApp_SignalR_");
    });

El backplane Redis publica todos los mensajes de SignalR en canales Redis. Cada instancia del servidor se suscribe a estos canales y entrega los mensajes a sus clientes locales. Este enfoque agrega latencia mínima (típicamente 1-2ms) mientras habilita el escalamiento horizontal.

Para implementaciones empresariales, Azure SignalR Service proporciona una alternativa administrada que maneja el escalamiento, la gestión de conexiones y la disponibilidad automáticamente.

Hubs Fuertemente Tipados para Seguridad en Tiempo de Compilación

Los hubs fuertemente tipados reemplazan las cadenas mágicas con métodos de interfaz, habilitando la verificación en tiempo de compilación de las llamadas a métodos del cliente:

INotificationClient.cscsharp
public interface INotificationClient
{
    Task ReceiveNotification(string title, string message, string severity);
    Task UpdateProgress(int percentage);
    Task TaskCompleted(Guid taskId, bool success);
}

// NotificationHub.cs
public class NotificationHub : Hub<INotificationClient>
{
    public async Task NotifyAll(string title, string message)
    {
        // Compile-time checking - no magic strings
        await Clients.All.ReceiveNotification(title, message, "info");
    }

    public async Task ReportProgress(string groupName, int percentage)
    {
        // IDE autocomplete works here
        await Clients.Group(groupName).UpdateProgress(percentage);
    }
}

La refactorización se vuelve más segura porque renombrar un método en la interfaz muestra inmediatamente todos los sitios de llamada que necesitan actualización.

Streaming de Datos con Canales SignalR

SignalR soporta streaming para escenarios donde los datos se producen incrementalmente, como actualizaciones de progreso, seguimiento de logs o datos de sensores en tiempo real. Tanto el streaming de servidor a cliente como de cliente a servidor están soportados.

StreamingHub.cscsharp
public class StreamingHub : Hub
{
    // Server-to-client streaming with IAsyncEnumerable
    public async IAsyncEnumerable<StockPrice> StreamStockPrices(
        string[] symbols,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var random = new Random();
        
        while (!cancellationToken.IsCancellationRequested)
        {
            foreach (var symbol in symbols)
            {
                yield return new StockPrice
                {
                    Symbol = symbol,
                    Price = random.NextDouble() * 1000,
                    Timestamp = DateTime.UtcNow
                };
            }
            
            await Task.Delay(1000, cancellationToken);
        }
    }

    // Client-to-server streaming
    public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
    {
        await foreach (var entry in stream)
        {
            // Process each log entry as it arrives
            await ProcessLogEntry(entry);
        }
    }
}

public record StockPrice(string Symbol, double Price, DateTime Timestamp);
public record LogEntry(string Level, string Message, DateTime Timestamp);

El cliente JavaScript consume el stream usando un iterador asíncrono:

streaming-client.tstypescript
const stream = connection.stream("StreamStockPrices", ["AAPL", "MSFT"]);

stream.subscribe({
    next: (price) => console.log(`${price.symbol}: $${price.price}`),
    error: (err) => console.error(err),
    complete: () => console.log("Stream completed")
});

Preguntas de Entrevista Comunes sobre SignalR

Las entrevistas técnicas frecuentemente cubren la arquitectura de SignalR y escenarios del mundo real. Las siguientes preguntas aparecen regularmente en sesiones de entrevista .NET:

Consejo de Entrevista

Es importante explicar el proceso de negociación de transporte: SignalR intenta primero WebSockets, luego recurre a Server-Sent Events, después Long Polling. Comprender cuándo se usa cada transporte y sus limitaciones es fundamental.

P: ¿Cómo maneja SignalR el estado de conexión cuando un cliente pierde conectividad temporalmente?

SignalR mantiene el estado de conexión en el servidor durante un período configurable. Con la reconexión automática habilitada, el cliente intenta reconectarse usando el mismo ID de conexión. Si tiene éxito, la conexión se reanuda sin perder membresías de grupo. Si la desconexión excede el timeout, se establece una nueva conexión y el cliente debe volver a unirse a los grupos.

P: ¿Cuál es la diferencia entre Clients.User() y Clients.Client()?

Clients.User(userId) apunta a todas las conexiones pertenecientes a un usuario autenticado específico—útil cuando un usuario tiene múltiples pestañas del navegador abiertas. Clients.Client(connectionId) apunta a una única conexión específica. El direccionamiento basado en usuario requiere autenticación y usa IUserIdProvider para mapear conexiones a IDs de usuario.

P: ¿Cómo implementar detección de presencia (mostrar quién está en línea)?

El seguimiento de conexiones en OnConnectedAsync y OnDisconnectedAsync es necesario. Los mapeos de usuario a conexión deben almacenarse en un diccionario concurrente o caché distribuido. Para implementaciones escaladas, se recomienda usar un almacenamiento compartido como Redis. Las actualizaciones de presencia deben transmitirse a los grupos relevantes cuando los usuarios se conectan o desconectan.

P: Explique el propósito de un backplane de SignalR.

Un backplane sincroniza mensajes entre múltiples instancias de servidor en un entorno con balanceo de carga. Sin él, un mensaje enviado desde el servidor A solo llega a los clientes conectados al servidor A. Redis y Azure SignalR Service son opciones comunes de backplane. El backplane publica mensajes a un canal compartido al que todos los servidores se suscriben.

Estrategias de Optimización de Rendimiento

El rendimiento de SignalR depende del tamaño de los mensajes, su frecuencia y el conteo de conexiones. La documentación de mejores prácticas de rendimiento de ASP.NET Core proporciona benchmarks detallados.

  • Usar protocolo MessagePack para serialización binaria—reduce el tamaño del payload 30-50% comparado con JSON
  • Agrupar mensajes cuando sea posible en lugar de enviar muchos mensajes pequeños
  • Limitar tamaños de grupo a miles de miembros; para audiencias más grandes, considerar patrones pub/sub
  • Habilitar compresión para payloads con mucho texto: .AddJsonProtocol().AddHubOptions(o => o.EnableDetailedErrors = false)
  • Establecer timeouts apropiados para liberar recursos de conexiones obsoletas rápidamente
csharp
// MessagePack configuration for smaller payloads
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = 
            MessagePackSerializerOptions.Standard
                .WithCompression(MessagePackCompression.Lz4Block);
    });

Conclusión

  • SignalR abstrae la selección de transporte (WebSockets, SSE, Long Polling) y proporciona un modelo de programación consistente para comunicación en tiempo real
  • Los Hubs centralizan la gestión de conexiones; los hubs fuertemente tipados agregan seguridad en tiempo de compilación para llamadas de métodos del cliente
  • Los grupos habilitan entrega dirigida de mensajes a subconjuntos de conexiones—volver a unirse a grupos después de reconexión
  • La autenticación se integra con la identidad de ASP.NET Core; pasar tokens mediante query string para conexiones WebSocket
  • Escalar horizontalmente con backplane Redis o Azure SignalR Service para sincronizar mensajes entre instancias de servidor
  • El streaming soporta entrega incremental de datos con IAsyncEnumerable para escenarios tanto de servidor a cliente como de cliente a servidor

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Desarrollador fullstack, fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 12 de agosto de 2026

Etiquetas

#signalr
#asp.net core
#tiempo-real
#websockets
#dotnet

Compartir

Artículos relacionados