.NET SignalR WebSockets C# Tempo Real

SignalR no .NET: comunicação em tempo real com WebSockets, grupos e autenticação

Guia completo de SignalR no ASP.NET Core: Hubs, WebSockets, grupos de conexão, autenticação JWT, scale-out com Redis e integração com React.

N
Neryx Digital Architects
3 de fevereiro de 2026
13 min de leitura
250 profissionais leram
Categoria: .NET Público: Times de engenharia e produto Etapa: Aprendizado

SignalR abstrai WebSockets, Server-Sent Events e Long Polling numa API unificada, deixando o servidor escolher automaticamente o melhor transporte disponível. O resultado: comunicação bidirecional em tempo real com poucas linhas de código — notificações ao vivo, dashboards atualizados automaticamente, chat, colaboração em tempo real.

Setup e primeiro Hub

dotnet add package Microsoft.AspNetCore.SignalR.Client   // cliente .NET (opcional)
// SignalR server já vem no pacote ASP.NET Core

// Program.cs: builder.Services.AddSignalR(options => { options.EnableDetailedErrors = builder.Environment.IsDevelopment(); options.KeepAliveInterval = TimeSpan.FromSeconds(15); options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); options.MaximumReceiveMessageSize = 32 * 1024; // 32KB por mensagem });

// Com Redis para scale-out (múltiplas instâncias): builder.Services.AddSignalR() .AddStackExchangeRedis(builder.Configuration[“Redis:ConnectionString”]!, options => { options.Configuration.ChannelPrefix = RedisChannel.Literal(“NeryxApp”); });

// Mapeando Hubs: app.MapHub<NotificacoesHub>(“/hubs/notificacoes”); app.MapHub<DashboardHub>(“/hubs/dashboard”); app.MapHub<ChatHub>(“/hubs/chat”);

Hub de notificações com autenticação JWT

// Hub com autenticação — apenas usuários autenticados se conectam:
[Authorize]
public class NotificacoesHub : Hub
{
    private readonly ILogger<NotificacoesHub> _logger;

    public NotificacoesHub(ILogger<NotificacoesHub> logger)
        => _logger = logger;

    // Chamado quando o cliente se conecta
    public override async Task OnConnectedAsync()
    {
        var userId = Context.UserIdentifier!; // vem do claim "sub" do JWT

        // Adiciona ao grupo pessoal do usuário (para notificações individuais)
        await Groups.AddToGroupAsync(Context.ConnectionId, $"user:{userId}");

        // Adiciona ao grupo da empresa (para notificações de tenant)
        var tenantId = Context.User!.FindFirst("tenant_id")?.Value;
        if (tenantId != null)
            await Groups.AddToGroupAsync(Context.ConnectionId, $"tenant:{tenantId}");

        _logger.LogInformation("Usuário {UserId} conectado. ConnectionId: {ConnId}",
            userId, Context.ConnectionId);

        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        _logger.LogInformation("Usuário {UserId} desconectado", Context.UserIdentifier);
        await base.OnDisconnectedAsync(exception);
    }

    // Método invocável pelo cliente (client → server)
    public async Task MarcarNotificacaoComoLida(Guid notificacaoId)
    {
        var userId = Context.UserIdentifier!;
        // ... lógica de negócio
        // Confirma de volta para o cliente
        await Clients.Caller.SendAsync("NotificacaoMarcada", notificacaoId);
    }
}

Enviando notificações de qualquer lugar via IHubContext

O grande diferencial do SignalR é poder enviar mensagens a partir de qualquer serviço — não apenas dentro do Hub:

// Serviço que envia notificações em tempo real:
public class NotificacaoService
{
    private readonly IHubContext<NotificacoesHub> _hub;
    private readonly AppDbContext _db;
public NotificacaoService(IHubContext&lt;NotificacoesHub&gt; hub, AppDbContext db)
{
    _hub = hub;
    _db = db;
}

// Notifica um usuário específico (qualquer dispositivo conectado)
public async Task NotificarUsuarioAsync(Guid userId, NotificacaoDto notificacao)
{
    // Persiste no banco
    _db.Notificacoes.Add(new Notificacao { UserId = userId, ...notificacao });
    await _db.SaveChangesAsync();

    // Envia em tempo real para todos os dispositivos do usuário
    await _hub.Clients
        .Group($"user:{userId}")
        .SendAsync("NovaNotificacao", notificacao);
}

// Notifica todos de um tenant (broadcast para empresa)
public async Task NotificarTenantAsync(Guid tenantId, string mensagem)
{
    await _hub.Clients
        .Group($"tenant:{tenantId}")
        .SendAsync("AlertaTenant", new { mensagem, timestamp = DateTime.UtcNow });
}

// Notifica todos os conectados (broadcast global — usar com cuidado)
public async Task BroadcastSistemaAsync(string mensagem)
{
    await _hub.Clients.All.SendAsync("MensagemSistema", new { mensagem });
}

}

// Uso em um endpoint — após salvar pedido, notifica cliente: [HttpPost(“pedidos”)] public async Task<IActionResult> CriarPedido([FromBody] CriarPedidoRequest request) { var pedido = await _pedidoService.CriarAsync(request);

// Notificação em tempo real — sem polling!
await _notificacaoService.NotificarUsuarioAsync(
    pedido.ClienteId,
    new NotificacaoDto("Pedido confirmado", $"Seu pedido #{pedido.Numero} foi recebido."));

return CreatedAtAction(nameof(GetPedido), new { id = pedido.Id }, null);

}

Dashboard de métricas ao vivo

// Hub para dashboard — sem autenticação de usuário, mas com política de role:
[Authorize(Policy = "requer-admin")]
public class DashboardHub : Hub
{
    // Clientes se inscrevem em métricas específicas
    public async Task InscreverMetrica(string metricaNome)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, $"metrica:{metricaNome}");
        await Clients.Caller.SendAsync("InscricaoConfirmada", metricaNome);
    }
public async Task CancelarMetrica(string metricaNome)
{
    await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"metrica:{metricaNome}");
}

}

// Worker que publica métricas a cada segundo: public class MetricasBroadcastWorker : BackgroundService { private readonly IHubContext<DashboardHub> _hub; private readonly IMetricasService _metricas;

protected override async Task ExecuteAsync(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        var pedidosAtivos = await _metricas.GetPedidosAtivosAsync();
        var usuariosOnline = await _metricas.GetUsuariosOnlineAsync();
        var receitaHora = await _metricas.GetReceitaUltimaHoraAsync();

        // Envia para quem está inscrito na métrica "pedidos"
        await _hub.Clients
            .Group("metrica:pedidos")
            .SendAsync("AtualizacaoMetrica", new { pedidosAtivos, timestamp = DateTime.UtcNow });

        // Envia para quem está inscrito na métrica "receita"
        await _hub.Clients
            .Group("metrica:receita")
            .SendAsync("AtualizacaoMetrica", new { receitaHora, usuariosOnline });

        await Task.Delay(TimeSpan.FromSeconds(1), ct);
    }
}

}

Cliente React com @microsoft/signalr

// npm install @microsoft/signalr

import { HubConnectionBuilder, LogLevel, HubConnectionState } from “@microsoft/signalr”; import { useEffect, useRef, useState } from “react”;

export function useNotificacoes(token: string) { const connectionRef = useRef<signalR.HubConnection | null>(null); const [notificacoes, setNotificacoes] = useState<Notificacao[]>([]); const [conectado, setConectado] = useState(false);

useEffect(() => {
    const connection = new HubConnectionBuilder()
        .withUrl("/hubs/notificacoes", {
            accessTokenFactory: () => token, // JWT injetado nos headers
        })
        .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) // backoff
        .configureLogging(LogLevel.Warning)
        .build();

    // Ouve o evento do servidor
    connection.on("NovaNotificacao", (notificacao: Notificacao) => {
        setNotificacoes(prev => [notificacao, ...prev]);
        // Toca som, mostra toast, etc.
    });

    connection.onreconnecting(() => setConectado(false));
    connection.onreconnected(() => setConectado(true));

    connection.start()
        .then(() => setConectado(true))
        .catch(err => console.error("Erro ao conectar SignalR:", err));

    connectionRef.current = connection;

    return () => {
        connection.stop();
    };
}, [token]);

// Invoca método no servidor
const marcarComoLida = (id: string) => {
    connectionRef.current?.invoke("MarcarNotificacaoComoLida", id);
};

return { notificacoes, conectado, marcarComoLida };

}

Scale-out: múltiplas instâncias com Redis Backplane

Por padrão, SignalR mantém conexões em memória — se você tem 3 instâncias da API, uma conexão de usuário existe em apenas uma delas. Para enviar mensagem a um usuário independente de qual instância recebeu o request, use o Redis Backplane:

// Já mostrado no setup, mas vale detalhar:
builder.Services.AddSignalR()
    .AddStackExchangeRedis("seu-redis:6379,password=...", options =>
    {
        // Prefixo para não colidir com outros dados no Redis
        options.Configuration.ChannelPrefix = RedisChannel.Literal("SignalR:NeryxApp");
    });

// Com isso: IHubContext.Clients.Group(“user:123”).SendAsync(…) // O Redis publica o evento e TODAS as instâncias entregam para conexões que elas gerenciam. // Funciona transparentemente — zero mudança no código do Hub.

Typed Hubs: contrato fortemente tipado

// Define interface dos métodos que o SERVER envia para o CLIENT:
public interface INotificacoesClient
{
    Task NovaNotificacao(NotificacaoDto notificacao);
    Task NotificacaoMarcada(Guid notificacaoId);
    Task AlertaTenant(AlertaDto alerta);
}

// Hub com tipagem — erros de nome de método viram erros de compilação: public class NotificacoesHub : Hub<INotificacoesClient> { public override async Task OnConnectedAsync() { await Groups.AddToGroupAsync(Context.ConnectionId, $“user:{Context.UserIdentifier}”); await base.OnConnectedAsync(); } }

// IHubContext também fica tipado: public class NotificacaoService { private readonly IHubContext<NotificacoesHub, INotificacoesClient> _hub;

public async Task NotificarAsync(Guid userId, NotificacaoDto dto)
{
    // Autocompletar e type-check no método:
    await _hub.Clients.Group($"user:{userId}").NovaNotificacao(dto);
}

}

Quando usar SignalR vs polling vs SSE

Use SignalR quando o cliente também precisa enviar dados para o servidor (chat, colaboração, jogos). Use Server-Sent Events (SSE) quando o fluxo é somente servidor → cliente (notificações, progresso de upload, feeds). Use polling apenas como fallback para ambientes sem WebSocket. Para firehose de eventos de alta frequência (>10/s por conexão), avalie se SSE + HTTP/2 não é mais eficiente que SignalR.

Conclusão

SignalR resolve comunicação em tempo real em .NET com elegância: Hub tipado, grupos de conexão, autenticação JWT nativa e scale-out transparente com Redis. O padrão de notificações individuais via grupo user:{id} cobre 80% dos casos de uso em aplicações SaaS — de alertas a dashboards ao vivo.

Se você precisa adicionar tempo real a uma aplicação .NET existente — notificações, progresso de tarefas, colaboração ou dashboards — a Neryx pode projetar e implementar a solução certa. Consultoria inicial gratuita.

Leitura complementar:

Quer transformar esse aprendizado em plano de ação?

Se o tema deste artigo se parece com o momento do seu time, podemos ajudar a decidir o próximo passo com clareza.

Falar com um especialista

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.