Destino Dev

Blog

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