SignalR com .NET 10: implementando comunicação em tempo real na prática
Em muitas aplicações, precisamos atualizar informações no front-end assim que um evento acontece no back-end.
Imagine um sistema de processamento de pedidos. Quando um pagamento é aprovado, o usuário precisa ser notificado imediatamente, sem atualizar a página.
Uma abordagem comum seria utilizar polling, fazendo requisições HTTP a cada poucos segundos para verificar se alguma informação mudou.
Embora funcione, essa estratégia pode gerar requisições desnecessárias, aumentar o consumo de recursos e introduzir latência entre o processamento de um evento e sua visualização.
É justamente nesse cenário que o ASP.NET Core SignalR se destaca.
Neste artigo, vamos entender como o SignalR funciona e implementar uma aplicação de notificações em tempo real utilizando .NET 10, Minimal APIs e JavaScript.
O que é SignalR?
O SignalR é uma biblioteca do ecossistema ASP.NET Core que permite comunicação em tempo real entre servidores e clientes.
Diferentemente de uma API REST tradicional, em que o cliente normalmente inicia cada requisição, o SignalR permite que o servidor envie informações diretamente para os clientes conectados.
Isso é especialmente útil em aplicações que exigem atualizações frequentes ou imediatas.
Alguns exemplos:
- Notificações em tempo real.
- Atualização de status de pedidos e pagamentos.
- Dashboards operacionais e financeiros.
- Sistemas de chat.
- Monitoramento de processos e serviços.
- Acompanhamento de tarefas executadas em background.
Como funciona a comunicação?
O SignalR utiliza diferentes mecanismos de transporte, selecionando automaticamente o mais adequado entre os disponíveis.
WebSockets: permite comunicação bidirecional persistente entre cliente e servidor. É o transporte preferencial quando está disponível.
Server-Sent Events (SSE): permite que o servidor envie eventos continuamente ao cliente utilizando HTTP.
Long Polling: mantém requisições HTTP pendentes até que existam informações disponíveis, realizando novas requisições conforme necessário.
Essa abstração permite que o desenvolvedor se concentre nas regras de negócio, sem precisar implementar manualmente toda a infraestrutura de comunicação.
Entendendo os Hubs
O principal componente do SignalR é o Hub.
Um Hub funciona como um ponto de comunicação entre servidor e clientes conectados.
Por meio dele, podemos:
- Receber chamadas dos clientes.
- Enviar mensagens para todas as conexões.
- Enviar mensagens para usuários específicos.
- Organizar conexões em grupos.
- Gerenciar eventos relacionados às conexões.
O SignalR fornece algumas opções para direcionar mensagens:
| Recurso | Finalidade |
|---|---|
Clients.All |
Enviar para todos os clientes conectados |
Clients.Caller |
Enviar para quem chamou o método |
Clients.Others |
Enviar para os demais clientes |
Clients.User(id) |
Enviar para as conexões de um usuário |
Clients.Group(name) |
Enviar para um grupo específico |
Clients.Client(id) |
Enviar para uma conexão específica |
Esses recursos são importantes quando precisamos evitar o envio indiscriminado de informações para todos os usuários.
Implementando com .NET 10
Vamos desenvolver um exemplo simples de acompanhamento do status de pedidos.
O cenário será o seguinte:
- Um cliente acessa uma página para acompanhar um pedido.
- O cliente estabelece uma conexão com o SignalR.
- A conexão entra em um grupo associado ao pedido.
- Uma API recebe uma atualização de status.
- O SignalR notifica imediatamente os clientes daquele grupo.
1. Criando o projeto
Primeiro, crie uma aplicação ASP.NET Core:
dotnet new web -n RealtimeOrders -f net10.0
cd RealtimeOrders
O SignalR já está disponível no framework compartilhado do ASP.NET Core, portanto não precisamos instalar um pacote NuGet adicional no servidor.
Crie a seguinte estrutura:
RealtimeOrders/
├── Hubs/
│ └── OrderHub.cs
├── wwwroot/
│ └── index.html
├── Program.cs
└── RealtimeOrders.csproj
2. Criando o Hub
Crie o arquivo Hubs/OrderHub.cs:
using Microsoft.AspNetCore.SignalR;
namespace RealtimeOrders.Hubs;
public sealed class OrderHub : Hub
{
public Task SubscribeToOrder(string orderId)
{
if (string.IsNullOrWhiteSpace(orderId))
{
throw new HubException("Invalid order ID.");
}
return Groups.AddToGroupAsync(
Context.ConnectionId,
$"order:{orderId}"
);
}
}
Nesse exemplo, utilizamos o conceito de Groups.
Cada pedido possui um grupo próprio, identificado por order:{orderId}.
Quando um cliente chama o método SubscribeToOrder, sua conexão passa a fazer parte daquele grupo.
Por exemplo:
order:123
├── Cliente A
├── Cliente B
└── Cliente C
order:456
├── Cliente D
└── Cliente E
Assim, podemos enviar atualizações apenas aos clientes interessados em determinado pedido.
Importante: neste exemplo, os grupos são públicos para facilitar a demonstração. Em produção, precisamos autenticar o usuário e verificar se ele possui permissão para acompanhar o pedido antes de adicioná-lo ao grupo.
3. Configurando o SignalR e a API
Agora vamos configurar o Program.cs:
using Microsoft.AspNetCore.SignalR;
using RealtimeOrders.Hubs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapHub<OrderHub>("/hubs/orders");
app.MapPost(
"/orders/{orderId}/status",
async (
string orderId,
UpdateOrderStatusRequest request,
IHubContext<OrderHub> hubContext
) =>
{
if (string.IsNullOrWhiteSpace(request.Status))
{
return Results.BadRequest(
"Status cannot be empty."
);
}
var notification = new OrderStatusChanged(
orderId,
request.Status,
DateTimeOffset.UtcNow
);
await hubContext.Clients
.Group($"order:{orderId}")
.SendAsync(
"OrderStatusChanged",
notification
);
return Results.Ok(notification);
}
);
app.Run();
record UpdateOrderStatusRequest(string Status);
record OrderStatusChanged(
string OrderId,
string Status,
DateTimeOffset OccurredAt
);
Aqui temos três pontos importantes.
AddSignalR()
Registra os serviços necessários para utilizar o SignalR na aplicação.
MapHub<OrderHub>()
Disponibiliza o endpoint responsável por receber conexões dos clientes.
IHubContext<OrderHub>
Permite enviar mensagens para clientes conectados sem precisar executar o envio dentro de um método do Hub.
Esse último recurso é especialmente interessante em aplicações back-end.
Com IHubContext, podemos publicar notificações a partir de APIs, serviços de aplicação, consumidores de filas ou processos em background.
Isso facilita a integração do SignalR com arquiteturas mais complexas.
4. Criando o cliente JavaScript
Vamos criar uma página HTML para receber as notificações.
No arquivo wwwroot/index.html, adicione:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>SignalR - Order Tracking</title>
</head>
<body>
<h1>Acompanhamento do pedido #123</h1>
<p id="connectionStatus">Conectando...</p>
<h2>Atualizações</h2>
<ul id="notifications"></ul>
<script src="https://cdn.jsdelivr.net/npm/@microsoft/signalr@10.0.11/dist/browser/signalr.min.js"></script>
<script>
const orderId = "123";
const statusElement =
document.getElementById("connectionStatus");
const notificationsElement =
document.getElementById("notifications");
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hubs/orders")
.withAutomaticReconnect()
.build();
connection.on("OrderStatusChanged", (message) => {
const item = document.createElement("li");
item.textContent =
`${message.status} - ` +
new Date(message.occurredAt).toLocaleString("pt-BR");
notificationsElement.prepend(item);
});
connection.onreconnecting(() => {
statusElement.textContent = "Reconectando...";
});
connection.onreconnected(async () => {
try {
await connection.invoke("SubscribeToOrder", orderId);
statusElement.textContent = "Conectado";
} catch (error) {
console.error("Subscription failed:", error);
}
});
connection.onclose(() => {
statusElement.textContent = "Desconectado";
setTimeout(start, 5000);
});
async function start() {
try {
await connection.start();
await connection.invoke(
"SubscribeToOrder",
orderId
);
statusElement.textContent = "Conectado";
} catch (error) {
console.error(error);
statusElement.textContent = "Falha na conexão";
setTimeout(start, 5000);
}
}
start();
</script>
</body>
</html>
O cliente realiza quatro operações principais:
- Estabelece uma conexão com o endpoint
/hubs/orders. - Registra um evento para receber atualizações.
- Inscreve a conexão no grupo do pedido
123. - Exibe as notificações recebidas sem atualizar a página.
Observe este trecho:
connection.on("OrderStatusChanged", (message) => {
console.log(message);
});
O nome OrderStatusChanged representa o evento enviado pelo back-end.
Quando o servidor executar:
await hubContext.Clients
.Group("order:123")
.SendAsync("OrderStatusChanged", notification);
Todos os clientes conectados àquele grupo receberão o evento.
A comunicação acontece sem que o navegador precise realizar novas requisições para consultar o status.
5. Executando e testando
Inicie a aplicação:
dotnet run --urls http://localhost:5000
Acesse no navegador:
http://localhost:5000
A página deve apresentar a indicação de conexão estabelecida.
Agora, abra um terminal e execute:
curl -X POST http://localhost:5000/orders/123/status \
-H "Content-Type: application/json" \
-d '{"status":"Pagamento aprovado"}'
A notificação deverá aparecer imediatamente na página.
Faça outro teste:
curl -X POST http://localhost:5000/orders/123/status \
-H "Content-Type: application/json" \
-d '{"status":"Pedido enviado para transporte"}'
O navegador receberá a nova atualização automaticamente.
Experimente abrir duas abas acompanhando o mesmo pedido.
Quando uma atualização for publicada, ambas receberão a informação.
Observe que essa API demonstra apenas a publicação de eventos: ela não persiste nem altera o estado real de um pedido no banco de dados.
SignalR em arquiteturas orientadas a eventos
Até aqui, implementamos um exemplo relativamente simples.
Entretanto, uma das aplicações mais interessantes do SignalR é sua integração com arquiteturas orientadas a eventos (Event-Driven Architecture).
Imagine uma aplicação distribuída responsável por processar pedidos.
O fluxo poderia ser:
[API de Pedidos]
|
v
[Message Broker]
|
v
[Worker de Pagamentos]
|
v
[Atualização do Banco]
|
v
[Evento: PaymentApproved]
|
v
[Serviço de Notificações]
|
v
[SignalR Hub]
|
v
[Clientes Conectados]
Nesse cenário, o SignalR não é responsável por processar pagamentos ou garantir a entrega dos eventos de negócio.
Sua responsabilidade é entregar atualizações em tempo real para os clientes conectados.
Por exemplo, um consumidor de mensagens do RabbitMQ ou Kafka pode processar um evento e acionar um serviço que utiliza IHubContext para notificar o usuário.
Essa separação traz benefícios importantes:
- Baixo acoplamento: os serviços de negócio não precisam conhecer os clientes conectados.
- Responsabilidades bem definidas: o processamento acontece nos serviços de domínio e aplicação; o SignalR atua na comunicação.
- Escalabilidade: podemos escalar consumidores e processamento independentemente da camada de notificações.
- Flexibilidade: diferentes eventos de negócio podem gerar notificações para aplicações web, desktop ou mobile.
Vale destacar que a integração deve acontecer após a confirmação do processamento de negócio. Em cenários que exigem confiabilidade, mecanismos como Outbox Pattern podem ajudar a evitar inconsistências entre persistência e publicação de eventos.
Boas práticas para utilizar SignalR em produção
Uma implementação funcional é apenas o primeiro passo.
Em ambientes de produção, alguns cuidados são fundamentais.
1. Não concentre regras de negócio nos Hubs
Evite transformar o Hub em uma classe responsável por validações complexas, persistência e orquestração.
Prefira manter essas responsabilidades em serviços especializados.
O Hub deve atuar principalmente como uma interface de comunicação.
2. Utilize autenticação e autorização
O SignalR suporta integração com mecanismos de autenticação do ASP.NET Core, incluindo JWT e cookies.
Em aplicações com informações privadas, utilize [Authorize] nos Hubs e valide permissões antes de incluir conexões em grupos.
Groups não substituem autorização.
Conhecer o identificador de um pedido não deve ser suficiente para acompanhar seus eventos.
Os endpoints HTTP que publicam ou alteram informações também precisam de proteção.
3. Trate desconexões e reconexões
Conexões em tempo real podem ser interrompidas por problemas de rede, mudanças de conectividade ou reinicialização de servidores.
O cliente JavaScript oferece suporte a reconexão automática:
.withAutomaticReconnect()
Por padrão, o cliente tenta reconectar após intervalos de 0, 2, 10 e 30 segundos.
Entretanto, essa configuração não resolve automaticamente falhas na primeira conexão.
Também é importante lembrar que a participação em grupos não é preservada após a reconexão de uma conexão.
Por isso, nosso exemplo executa novamente SubscribeToOrder quando a conexão é restabelecida.
4. Considere a escalabilidade horizontal
Quando uma aplicação utiliza várias instâncias, as conexões dos clientes podem estar distribuídas entre diferentes servidores.
Nesse cenário, publicar um evento em uma instância não significa que todos os clientes das outras instâncias receberão a mensagem automaticamente.
Duas alternativas comuns são:
Redis Backplane
Permite a comunicação entre instâncias do SignalR utilizando Redis como infraestrutura de distribuição de mensagens.
Azure SignalR Service
Serviço gerenciado que centraliza o gerenciamento de conexões e facilita a escalabilidade.
Ao utilizar Redis Backplane com múltiplas instâncias, também é necessário considerar a afinidade de sessão (sticky sessions), salvo nas exceções previstas pela arquitetura de hospedagem.
5. Não utilize SignalR como substituto de um broker
SignalR, RabbitMQ e Kafka resolvem problemas diferentes.
| Tecnologia | Responsabilidade principal |
|---|---|
| SignalR | Comunicação em tempo real com clientes conectados |
| RabbitMQ | Distribuição e processamento de mensagens |
| Kafka | Plataforma de eventos com retenção e capacidade de replay |
| Redis | Cache, armazenamento em memória e suporte a distribuição de mensagens |
SignalR não fornece, por padrão, armazenamento durável ou garantia de entrega das notificações para clientes desconectados.
Se a aplicação precisa preservar notificações, implemente persistência e disponibilize uma API para recuperar informações perdidas.
Uma estratégia comum é carregar o estado atual por uma API REST e utilizar SignalR para receber atualizações posteriores.
6. Monitore as conexões
Em sistemas de maior escala, monitore:
- Quantidade de conexões ativas.
- Taxa de conexões e desconexões.
- Frequência de reconexões.
- Falhas no envio de mensagens.
- Utilização de CPU e memória.
- Latência entre a geração do evento e o recebimento pelo cliente.
Logs estruturados, métricas e ferramentas de observabilidade ajudam a identificar gargalos e problemas de conectividade.
Quando utilizar SignalR?
SignalR é uma boa opção quando a aplicação precisa entregar informações aos clientes assim que elas acontecem.
É especialmente útil em dashboards, sistemas operacionais, notificações e acompanhamento de processos.
Por outro lado, aplicações que precisam apenas consultar dados esporadicamente podem continuar utilizando APIs REST tradicionais.
A necessidade de comunicação em tempo real deve justificar o custo adicional de manter conexões persistentes.
Além disso, SignalR não substitui mecanismos de processamento assíncrono confiável, filas ou persistência de eventos.
Conclusão
Implementar comunicação em tempo real com .NET tornou-se relativamente simples com ASP.NET Core SignalR.
Com poucas configurações, conseguimos criar conexões bidirecionais, enviar mensagens, organizar clientes em grupos e integrar notificações a serviços de aplicação.
O maior desafio, entretanto, não costuma estar na criação do Hub.
Está nas decisões de arquitetura relacionadas à segurança, escalabilidade, confiabilidade, reconexões e integração com os processos de negócio.
Quando utilizado com responsabilidades bem definidas, o SignalR é uma excelente ferramenta para complementar aplicações distribuídas e melhorar a experiência dos usuários.
Referências
- ASP.NET Core SignalR — Documentação oficial
- Utilizando Hubs
- Gerenciamento de usuários e grupos
- Cliente JavaScript
- Hospedagem e escalabilidade
E você, já utilizou SignalR em aplicações .NET? Em quais cenários a comunicação em tempo real fez diferença na arquitetura dos seus projetos?
Compartilhe sua experiência nos comentários!
Top comments (0)