SignalR com Blazor: criar chat em tempo real com reconexão e fallback
Por John Pires · 16 de março de 2026

Introducao ao problema
O que vamos resolver
Aplicações web modernas exigem comunicação em tempo real: chats, notificações, atualizacoes ao vivo. Para quem comeca com C# e Blazor, o desafio e entender como estabelecer uma conexao resiliente entre cliente e servidor, tratar reconexoes automáticas e oferecer fallback quando WebSockets nao estiver disponivel.
Este artigo mostra passo a passo um chat simples usando ASP.NET Core SignalR e Blazor WebAssembly (hosted). Foco: codigo funcional, comportamento de reconexao automatica e tentativa de fallback para LongPolling quando necessario.
Analogias rapidas
Pense no SignalR como uma linha direta entre cliente e servidor: idealmente voce usa fibra optica (WebSockets). Se a fibra cair, voce tenta via radio (ServerSentEvents) e, por fim, bate um carro com mensagem (LongPolling). O objetivo e manter a conversa viva sem perder mensagens importantes.
Vamos mostrar o setup minimo, exemplos de codigo reais e pontos de falha comuns para que voce consiga montar seu primeiro chat totalmente funcional.
Pre-requisitos e setup
Ferramentas e templates
Voce precisa de .NET 6+ SDK e de um editor como VS Code ou Visual Studio. Para acelerar, use o template "Blazor WebAssembly App" com a opcao "ASP.NET Core hosted". Isso cria um projeto Server, Client (Blazor WASM) e Shared para DTOs.
Comandos basicos se preferir terminal:
dotnet new blazorwasm -ho -o ChatApp
cd ChatApp
code .
O comando acima cria a estrutura e abre no VS Code. A partir dai vamos alterar o servidor para expor um Hub e o cliente para se conectar com reconexao e fallback.
Pacotes necessarios
No servidor, o SignalR ja vem com ASP.NET Core. No cliente Blazor WASM voce precisa instalar o cliente SignalR:
dotnet add Client/ package Microsoft.AspNetCore.SignalR.Client
Esse pacote fornece HubConnection e HubConnectionBuilder usados no componente Blazor para conectar ao servidor.
Garanta tambem que CORS esteja configurado no servidor para permitir chamadas do cliente hospedado.
Tutorial guiado passo a passo
1) Criando o ChatHub (servidor)
O Hub e a antena do servidor que recebe e distribui mensagens. Crie um arquivo ChatHub.cs dentro do projeto Server. Ele vai expor um metodo SendMessage que recebe um nome e texto e faz broadcast para todos os clientes conectados.
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// Envia para todos os clientes conectados
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
O que esse codigo faz: implementa um Hub simples com um metodo telefonado SendMessage. Sempre que chamado, o servidor dispara o evento ReceiveMessage em todos os clientes.
Depois de adicionar o Hub, precisamos mapear a rota no pipeline HTTP do servidor.
2) Configurando Program.cs (servidor)
Edite Program.cs do projeto Server para registrar o Hub e configurar CORS. Use uma politica permissiva para desenvolvimento hospedado localmente.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().SetIsOriginAllowed(_ => true);
});
});
builder.Services.AddSignalR();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseCors("AllowAll");
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToFile("index.html");
app.Run();
O que acontece: registamos SignalR e habilitamos CORS. MapHub expoe o Hub em /chathub. No desenvolvimento este setup e suficiente; em producao restrinja as origens.
3) DTO compartilhado
Crie na pasta Shared uma classe simples para representar mensagens (opcional, facilita LinQ e armazenamento posterior):
public record ChatMessage(string User, string Text, DateTime SentAt);
Contexto: nao e obrigatorio para o funcionamento do Hub, mas util quando voce quer persistir mensagens ou transmitir objetos tipados entre Client e Server.
4) Componente Blazor que consome o Hub
No projeto Client, crie o componente Pages/Chat.razor. Esse sera o front-end do chat que cria e gerencia a conexao com HubConnection, trata eventos de reconexao e implementa fallback para LongPolling quando necessario.
Antes do codigo, explicacao: vamos criar a conexao com HubConnectionBuilder apontando para "/chathub". Usaremos AutomaticReconnect para reconectar de forma exponencial. Se StartAsync falhar (ex: WebSockets bloqueado), tentaremos forcar LongPolling.
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
<h3>Chat em tempo real</h3>
<div>
<input @bind="user" placeholder="Seu nome" />
<input @bind="message" placeholder="Mensagem" @onkeydown="HandleEnter" />
<button @onclick="Send">Enviar</button>
</div>
<ul>
@foreach(var m in messages)
{
<li><strong>@m.User</strong> (@m.SentAt.ToLocalTime()): @m.Text</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private string user = "user";
private string message = string.Empty;
private List<ChatMessage> messages = new();
protected override async Task OnInitializedAsync()
{
// Constrói a conexao (padrao: negotiate -> websockets ou sse)
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithAutomaticReconnect() // tenta reconectar automaticamente
.Build();
hubConnection.On<string,string>("ReceiveMessage", (user, message) => {
messages.Add(new ChatMessage(user, message, DateTime.UtcNow));
InvokeAsync(StateHasChanged);
});
// Hooks para informar sobre reconexao
hubConnection.Reconnecting += error => {
messages.Add(new ChatMessage("system", "Reconectando...", DateTime.UtcNow));
InvokeAsync(StateHasChanged);
return Task.CompletedTask;
};
hubConnection.Reconnected += connectionId => {
messages.Add(new ChatMessage("system", "Reconectado [" + connectionId + "]", DateTime.UtcNow));
InvokeAsync(StateHasChanged);
return Task.CompletedTask;
};
hubConnection.Closed += async (error) => {
messages.Add(new ChatMessage("system", "Conexao fechada. Tentando reconectar...", DateTime.UtcNow));
InvokeAsync(StateHasChanged);
// opcional: tentar start manual com fallback
await TryStartWithFallback();
};
// Inicia conexao com tentativa de fallback se necessario
await TryStartWithFallback();
}
private async Task TryStartWithFallback()
{
if (hubConnection == null) return;
try
{
await hubConnection.StartAsync();
messages.Add(new ChatMessage("system", "Conectado (padrao)", DateTime.UtcNow));
}
catch (Exception)
{
// Tenta forcar LongPolling como fallback
messages.Add(new ChatMessage("system", "Padrao falhou. Tentando fallback LongPolling...", DateTime.UtcNow));
var builder = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"), options => { options.Transports = HttpTransportType.LongPolling; })
.WithAutomaticReconnect();
hubConnection = builder.Build();
hubConnection.On<string,string>("ReceiveMessage", (u, m) => {
messages.Add(new ChatMessage(u, m, DateTime.UtcNow));
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
messages.Add(new ChatMessage("system", "Conectado via LongPolling", DateTime.UtcNow));
}
}
private async Task Send()
{
if (hubConnection == null) return;
if (string.IsNullOrWhiteSpace(message)) return;
try
{
await hubConnection.SendAsync("SendMessage", user, message);
message = string.Empty;
}
catch (Exception ex)
{
messages.Add(new ChatMessage("system", "Erro ao enviar: " + ex.Message, DateTime.UtcNow));
}
}
private async Task HandleEnter(KeyboardEventArgs e)
{
if (e.Key == "Enter") await Send();
}
}
O que esse componente faz: cria e inicia conexao, registra handlers ReceiveMessage, Reconnecting, Reconnected, Closed. Em Closed, chama TryStartWithFallback que tenta StartAsync; se falhar, reconstrói a conexao forcando LongPolling e inicia novamente.
5) Ajustes de production e observacoes
No ambiente de producao, remova a politica AllowAll do CORS e limite origens. Tambem avalie usar autenticao e autorizacao no Hub se for chat autenticado.
Para logs detalhados ative o logging em ambos os projetos. Em cenarios reais, considere bufferizar mensagens ao reconectar para evitar perda de mensagens.
Resultado final funcionando
Como testar localmente
1) Execute o projeto (geralmente dotnet run no diretorio do Server ou "F5" no VS). 2) Abra dois navegadores distintas apontando para a aplicacao (ex: https://localhost:5001). 3) No componente Chat preencha nomes diferentes e envie mensagens. Voce deve ver as mensagens aparecerem em tempo real nos dois clientes.
Se voce desconectar a rede temporariamente, observe os eventos de reconexao: Reconnecting e Reconnected aparecem como mensagens de sistema no chat.
O que acontece nos bastidores
O SignalR faz negotiate com o servidor e tenta o melhor transporte disponivel. AutomaticReconnect gerencia tentativas de reconexao com backoff exponencial. Nosso TryStartWithFallback detecta erro inicial e forca LongPolling caso o transporte ideal seja bloqueado (muito util em proxies corporativos).
Com esse comportamento, a aplicacao tolera quedas e ambientes restritivos, mantendo a comunicacao essencial do chat.
Erros comuns, armadilhas e dicas praticas
Erro: "WebSocket is not available"
Isso ocorre quando o servidor ou o ambiente nao permite WebSockets (algum proxy ou configuracao de IIS). Solucao imediata: usar fallback para LongPolling como mostrado. Em producao, verifique a configuracao do servidor web (IIS/Nginx) para habilitar WebSockets.
Armadilha: CORS bloqueando a conexao
Se o cliente nao conseguir conectar, abra o console do navegador e veja erros de CORS. Certifique-se de que o servidor addPolicy.AllowCredentials() e SetIsOriginAllowed ou especifique origens explicitamente. Evite AllowAll em producao.
Dicas de performance e confiabilidade
- Use mensagens compactas: envie apenas o necessario para reduzir latencia.
- Grant rate limits: proteja seu Hub contra flood de mensagens por cliente.
- Persistencia: se mensagens sao criticas, salve em banco (EF Core) e reconcilie ao reconectar.
Para debug, ative logs do SignalR no cliente com HubConnectionBuilder.ConfigureLogging e no servidor configure logging para Microsoft.AspNetCore.SignalR.
Desafio para o leitor praticar
1) Persistir mensagens com EF Core
Adicione um DbContext no projeto Server e persista cada mensagem recebida no Hub. Ao conectar, envie as ultimas 50 mensagens para o cliente. Isso exige criar endpoints ou usar chamadas no Hub para recuperar historico.
2) Unread messages e reconexao
Implemente um mecanismo onde o servidor identifica mensagens enviadas enquanto o cliente estava desconectado e reentrega apenas as perdidas quando o cliente reconectar (use id sequencial em ChatMessage e metodo GetMessagesSince(lastId)).
3) Autenticacao no Hub
Integre autenticacao JWT no cliente e autentique o usuario no Hub usando [Authorize] e Context.User. Proteja endpoints para que apenas usuarios autenticados enviem e leiam mensagens.
Conclusao pratica
Mostramos um caminho completo e reproducivel para criar um chat com SignalR e Blazor WebAssembly: Hub no servidor, cliente com HubConnectionBuilder, automatic reconnect e tentativa de fallback para LongPolling. Tudo em codigo real e funcional.
Os pontos chave para iniciantes: entender a sequencia de tentativa de transporte, usar AutomaticReconnect para robustez e implementar fallback manual quando necessario. Esses elementos tornam sua aplicacao mais resiliente em ambientes variaveis.
Agora e sua vez: monte o projeto usando os trechos acima, teste desconexoes e tente os desafios propostos para internalizar as tecnicas. Boas praticas incluem logs, limites de taxa e persistencia de mensagens.
Assuntos: C#, blazor, signalr, reconexao, longpolling
Leia também
Soft delete no EF Core: filtros globais, auditoria e restauração
Soft delete com EF Core evita perda de dados, permite auditoria e restauração. Aprenda o jeito errado (filtros manuais) e o jeito certo (global query filters…
Tratar concorrência otimista e retries com EF Core e Polly em ASP.NET Core
Combina rowversion, tratamento de DbUpdateConcurrencyException e políticas Polly para retries seguros. Estratégia prática: recriar DbContext, mesclar ou…
Upload de arquivos com ASP.NET Core Minimal APIs: streaming e progresso
Guia prático para implementar upload de arquivos com ASP.NET Core Minimal APIs: streaming para evitar OOM, reporting de progresso via polling e dicas para…
Minimal APIs + SSE: enviar progresso de jobs para clientes Blazor passo a passo
Guia passo a passo para usar Minimal APIs + SSE e enviar progresso de jobs para clientes Blazor. Do setup ao código funcional: servidor com endpoint SSE…
Autenticação JWT em Minimal APIs: refresh tokens seguros com HttpOnly
Guia prático para implementar refresh tokens seguros com cookies HttpOnly em Minimal APIs ASP.NET Core: conceitos, passos comentados, exemplos em C# e…
Multitenancy em ASP.NET Core com EF Core: discriminator, schema e database
Comparação prática entre discriminator, schema e database para multitenancy em ASP.NET Core com EF Core. Implementação de TenantProvider, DbContext dinâmico…