Partilhar via


ASP.NET Core BlazorSignalR orientações

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como configurar e gerenciar conexões SignalR em aplicativos Blazor.

Para obter orientações gerais sobre a configuração do ASP.NET Core SignalR, consulte os tópicos na área de Visão geral de do ASP.NET Core SignalR da documentação, especialmente configuração do ASP.NET Core SignalR.

Os aplicativos do lado do servidor usam ASP.NET Core SignalR para se comunicar com o navegador. SignalRas condições de hospedagem e dimensionamento aplicam-se a aplicativos de servidor.

Blazor funciona melhor ao usar WebSockets como transporte para SignalR, devido à menor latência, confiabilidade e segurança. Long Polling é usado por SignalR quando WebSockets não está disponível ou quando o aplicativo está explicitamente configurado para usar Long Polling.

Azure SignalR Service com reconexão com monitoração de estado

O Serviço SignalR do Azure com SDK v1.26.1 ou posterior dá suporte a SignalR de reconexão com monitoração de estado (WithStatefulReconnect).

Compactação WebSocket para componentes do Servidor Interativo

Por padrão, os componentes do Servidor Interativo:

  • Habilite a compressão para conexões WebSocket . DisableWebSocketCompression (padrão: false) controla a compactação do WebSocket.

  • Adotar uma diretiva CSP (Política de Segurança de Conteúdo) definida como frame-ancestors, que é padrão e apenas permite incorporar o aplicativo em uma 'self' da origem a partir da qual o aplicativo é servido, quando a compactação está habilitada ou quando uma configuração para o contexto WebSocket é fornecida.

O CSP padrão frame-ancestors pode ser alterado definindo o valor de ContentSecurityFrameAncestorsPolicy para null se você quiser configurar o CSP de forma centralizada ou 'none' para uma política ainda mais rigorosa. Quando o CSP frame-ancestors é gerenciado de forma centralizada, deve-se tomar cuidado para aplicar uma política sempre que o primeiro documento for renderizado. Não recomendamos remover a política completamente, pois isso tornará o aplicativo vulnerável a ataques. Para obter mais informações, consulte Impor uma política de segurança de conteúdo para ASP.NET Core Blazor e o Guia CSP do MDN.

Use ConfigureWebSocketAcceptContext para configurar o WebSocketAcceptContext para as conexões WebSocket usadas pelos componentes do servidor. Por padrão, uma política que permite a compactação e define um CSP para os ancestrais do quadro definidos em ContentSecurityFrameAncestorsPolicy é aplicada.

Exemplos de uso:

Desative a compactação definindo DisableWebSocketCompression como true, o que reduz a vulnerabilidade do aplicativo para atacar mas pode resultar em desempenho reduzido:

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.DisableWebSocketCompression = true)

Quando a compactação estiver habilitada, configure um CSP mais rigoroso com um valor de frame-ancestors (aspas únicas necessárias), que permite a compactação WebSocket, mas impede que os navegadores incorporem a aplicação num 'none':

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

Quando a compactação estiver habilitada, remova o frame-ancestors CSP definindo ContentSecurityFrameAncestorsPolicy como null. Este cenário só é recomendado para aplicativos que definam o CSP de forma centralizada:

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)

Importante

Os navegadores aplicam diretrizes CSP provenientes de múltiplos cabeçalhos CSP utilizando as diretrizes de política mais rígidas. Portanto, um desenvolvedor não pode adicionar uma política de frame-ancestors mais fraca do que 'self' de propósito ou por engano.

Aspas simples são necessárias no valor da cadeia de caracteres passado para ContentSecurityFrameAncestorsPolicy:

Valores não suportados:none, self

✔️ Valores suportados:'none', 'self'

Opções adicionais incluem a especificação de uma ou mais fontes de host e fontes de esquema.

Para obter as implicações de segurança, consulte Diretrizes de mitigação de ameaças para ASP.NET Core Blazor de renderização interativa do lado do servidor. Para obter mais informações, consulte Impor uma política de segurança de conteúdo para ASP.NET Core Blazor e CSP: frame-ancestors (documentação MDN).

Desativar a compactação de resposta no Hot Reload

Quando usar o Hot Reload, desative o middleware de compressão de resposta no ambiente Development. Se o código padrão de um modelo de projeto é usado ou não, sempre chame UseResponseCompression primeiro no pipeline de processamento de solicitação.

No ficheiro Program:

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
}

Negociação entre origens do lado do cliente SignalR para autenticação

Esta seção explica como configurar o cliente subjacente do SignalRpara enviar credenciais, como cookies ou cabeçalhos de autenticação HTTP.

Use SetBrowserRequestCredentials para definir Include em solicitações de fetch de origem cruzada.

IncludeRequestCredentialsMessageHandler.cs:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
        return base.SendAsync(request, cancellationToken);
    }
}

Onde uma conexão de hub é criada, atribua o HttpMessageHandler à opção HttpMessageHandlerFactory:

private HubConnectionBuilder? hubConnection;

...

hubConnection = new HubConnectionBuilder()
    .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
    {
        options.HttpMessageHandlerFactory = innerHandler => 
            new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
    }).Build();

O exemplo anterior configura a URL de conexão do hub para o endereço URI absoluto em /chathub. O URI também pode ser definido através de uma cadeia de caracteres, por exemplo https://signalr.example.com, ou através de configuração. Navigation é um NavigationManager injetado.

Para obter mais informações, consulte SignalR .

Renderização do lado do cliente

Se a pré-renderização estiver configurada, a pré-renderização ocorrerá antes que a conexão do cliente com o servidor seja estabelecida. Para obter mais informações, consulte Persistência de estado pré-renderizado do núcleo ASP.NETBlazor.

Se a pré-renderização estiver configurada, a pré-renderização ocorrerá antes que a conexão do cliente com o servidor seja estabelecida. Para obter mais informações, consulte os seguintes artigos:

Tamanho do estado pré-renderizado e limite de tamanho da mensagem SignalR

Um estado pré-renderizado de tamanho grande pode exceder o limite de tamanho da mensagem do circuito Blazor do SignalR, resultando no seguinte:

  • O circuito SignalR falha ao inicializar com um erro no cliente: Circuit host not initialized.
  • A interface de reconexão no cliente aparece quando o circuito falha. A recuperação não é possível.

Para resolver o problema, use uma das seguintes abordagens:

  • Reduza a quantidade de dados que você está colocando no estado pré-renderizado.
  • Aumente o limite de tamanho da mensagem SignalR. AVISO: Aumentar o limite pode aumentar o risco de ataques de negação de serviço (DoS).

Recursos adicionais do lado do cliente

Usar afinidade de sessão (sessões adesivas) para hospedagem de web farm do lado do servidor

Quando mais de um servidor back-end está em uso, a aplicação deve implementar afinidade de sessão, também chamada de sessões persistentes . A afinidade de sessão garante que o circuito de um cliente se reconecte ao mesmo servidor se a conexão for interrompida, o que é importante porque o estado do cliente é mantido apenas na memória do servidor que primeiro estabeleceu o circuito do cliente.

O seguinte erro é gerado por um aplicativo que não habilitou a afinidade de sessão em uma web farm:

Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Para obter mais informações sobre afinidade de sessão com a hospedagem do Serviço de Aplicativo do Azure, consulte Hospedagem e implementação de aplicações Blazor do lado do servidor ASP.NET Core.

Azure SignalR Serviço

O SignalR Serviço Azure funciona em conjunto com o hub SignalR da aplicação para dimensionar uma aplicação do lado do servidor para um grande número de conexões simultâneas. Além disso, o alcance global do serviço e os data centers de alto desempenho ajudam significativamente na redução da latência devido à geografia.

O serviço não é necessário para Blazor aplicativos hospedados no Serviço de Aplicativo do Azure ou nos Aplicativos de Contêiner do Azure, mas pode ser útil em outros ambientes de hospedagem:

  • Para permitir a expansão da conexão.
  • Gerir a distribuição global.

Para mais informações, consulte Blazor.

Opções do manipulador de circuitos do lado do servidor

Configure o circuito com CircuitOptions. Exiba os valores padrão na fonte de referência .

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Leia ou defina as opções no arquivo Program com um delegado de opções para AddInteractiveServerComponents. O espaço reservado {OPTION} representa a opção e o espaço reservado {VALUE} é o valor.

No ficheiro Program:

builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
    options.{OPTION} = {VALUE};
});

Leia ou defina as opções no arquivo Program com um delegado de opções para AddServerSideBlazor. O espaço reservado {OPTION} representa a opção e o espaço reservado {VALUE} é o valor.

No ficheiro Program:

builder.Services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Leia ou defina as opções em Startup.ConfigureServices com um delegado de opções em AddServerSideBlazor. O espaço reservado {OPTION} representa a opção e o espaço reservado {VALUE} é o valor.

Em Startup.ConfigureServices de Startup.cs:

services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Para configurar o HubConnectionContext, use HubConnectionContextOptions com AddHubOptions. Veja as predefinições para as opções de contexto de conexão do hub na fonte de referência . Para obter descrições de opções na documentação do SignalR, consulte ASP.NET Core SignalR configuration. O espaço reservado {OPTION} representa a opção e o espaço reservado {VALUE} é o valor.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

No ficheiro Program:

builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

No ficheiro Program:

builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Em Startup.ConfigureServices de Startup.cs:

services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Advertência

O valor padrão de MaximumReceiveMessageSize é 32 KB. Aumentar o valor pode aumentar o risco de ataques de negação de serviço (DoS) .

Blazor depende de MaximumParallelInvocationsPerClient definido como 1, que é o valor padrão. Para mais informações, veja MaximumParallelInvocationsPerClient > 1 causa problemas no upload de arquivos no modo Blazor Server (dotnet/aspnetcore #53951).

Para obter informações sobre gerenciamento de memória, consulte Gerenciar memória em aplicativos implantados do lado Blazor do servidor ASP.NET Core.

Opções de hub Blazor

Configure as opções do MapBlazorHub para controlar o HttpConnectionDispatcherOptions do hub Blazor. Exiba os padrões para as opções do dispatcher de conexão de hub em fonte de referência. O espaço reservado {OPTION} representa a opção e o espaço reservado {VALUE} é o valor.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Coloque a chamada para app.MapBlazorHub após a chamada para app.MapRazorComponents no arquivo de Program do aplicativo:

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

A configuração do hub usado pelo AddInteractiveServerRenderMode com MapBlazorHub falha com um AmbiguousMatchException:

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.

Para contornar o problema para aplicativos destinados ao .NET 8/9, adote a seguinte abordagem.

Na parte superior do Program ficheiro, adicione uma using instrução para Microsoft.AspNetCore.Http.Connections:

using Microsoft.AspNetCore.Http.Connections;

Onde MapRazorComponents é chamado, encadeie a seguinte convenção de ponto final para o RazorComponentsEndpointConventionBuilder:

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .Add(e =>
    {
        var metadata = e.Metadata;
        var dispatcherOptions = metadata.OfType<HttpConnectionDispatcherOptions>().FirstOrDefault();

        if (dispatcherOptions != null)
        {
            dispatcherOptions.CloseOnAuthenticationExpiration = true;
        }
    });

Para obter mais informações, consulte os seguintes recursos:

Forneça as opções para app.MapBlazorHub no arquivo Program do aplicativo:

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Forneça as opções para app.MapBlazorHub na configuração de roteamento dos endpoints:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub(options =>
    {
        options.{OPTION} = {VALUE};
    });
    ...
});

Tamanho máximo da mensagem recebida

Esta secção aplica-se apenas a projetos que implementem SignalR.

O tamanho máximo de mensagem de entrada SignalR permitido para métodos de hub é limitado pelo HubOptions.MaximumReceiveMessageSize (padrão: 32 KB). SignalR mensagens maiores que MaximumReceiveMessageSize geram um erro. A estrutura não impõe um limite ao tamanho de uma mensagem SignalR do hub para um cliente.

Quando o SignalR registo não está definido para Depuração ou Rastreamento, um erro de tamanho de mensagem só aparece nas ferramentas de desenvolvedor do navegador (console):

Erro: Conexão desconectada com o erro 'Erro: O servidor retornou um erro ao fechar: Conexão fechada com um erro.'.

Quando SignalR do lado do servidor é definido como Debug ou Trace, o log do lado do servidor sinaliza um InvalidDataException para um erro de tamanho de mensagem.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      ...
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Erro:

System.IO.InvalidDataException: O tamanho máximo da mensagem de 32768B foi excedido. O tamanho da mensagem pode ser configurado em AddHubOptions.

Uma abordagem envolve aumentar o limite definindo MaximumReceiveMessageSize no arquivo Program. O exemplo a seguir define o tamanho máximo da mensagem de recebimento para 64 KB:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Aumentar o limite do tamanho das mensagens recebidas SignalR tem o custo de exigir mais recursos do servidor e, consequentemente, aumenta o risco de ataques de negação de serviço (DoS) . Além disso, a leitura de uma grande quantidade de conteúdo na memória como cadeias de caracteres ou matrizes de bytes pode resultar em alocações que não colaboram bem com o coletor de lixo, causando penalizações adicionais de desempenho.

Uma opção melhor para ler grandes payloads é enviar o conteúdo em pedaços menores e processar o payload como um Stream. Isso pode ser usado ao ler grandes cargas úteis de JSON de interoperabilidade de JavaScript (JS) ou se os dados de interoperabilidade JS estiverem disponíveis como dados brutos. Para um exemplo que demonstra o envio de grandes cargas binárias em aplicações do lado do servidor que utilizam técnicas semelhantes ao componente InputFile, consulte o aplicativo de exemplo de Envio Binário e o Exemplo de Componente BlazorInputLargeTextArea.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Formulários que processam grandes cargas úteis ao longo de SignalR também podem usar diretamente a interoperabilidade de streaming JS. Para obter mais informações, consulte Chamar métodos .NET a partir de funções JavaScript no ASP.NET Core Blazor. Para obter um exemplo de formulários que transmite <textarea> dados para o servidor, consulte Solucionar problemas dos formulários do ASP.NET Core Blazor.

Uma abordagem envolve aumentar o limite definindo MaximumReceiveMessageSize no arquivo Program. O exemplo a seguir define o tamanho máximo da mensagem de recebimento para 64 KB:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Aumentar o limite do tamanho das mensagens recebidas SignalR tem o custo de exigir mais recursos do servidor e, consequentemente, aumenta o risco de ataques de negação de serviço (DoS) . Além disso, a leitura de uma grande quantidade de conteúdo na memória como cadeias de caracteres ou matrizes de bytes pode resultar em alocações que não colaboram bem com o coletor de lixo, causando penalizações adicionais de desempenho.

Uma opção melhor para ler grandes payloads é enviar o conteúdo em pedaços menores e processar o payload como um Stream. Isso pode ser usado ao ler grandes cargas úteis de JSON de interoperabilidade de JavaScript (JS) ou se os dados de interoperabilidade JS estiverem disponíveis como dados brutos. Para obter um exemplo que demonstra o envio de grandes cargas binárias em Blazor Server que usa técnicas semelhantes ao componente InputFile, consulte a aplicação de exemplo de Submissão Binária e o Exemplo de Componente BlazorInputLargeTextArea.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Formulários que processam grandes cargas úteis ao longo de SignalR também podem usar diretamente a interoperabilidade de streaming JS. Para obter mais informações, consulte Chamar métodos .NET a partir de funções JavaScript no ASP.NET Core Blazor. Para obter um exemplo de formulários que transmite dados <textarea> num aplicativo Blazor Server, consulte Solucionar problemas de formulários do ASP.NET Core Blazor.

Aumente o limite definindo MaximumReceiveMessageSize em Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Aumentar o limite do tamanho das mensagens recebidas SignalR tem o custo de exigir mais recursos do servidor e, consequentemente, aumenta o risco de ataques de negação de serviço (DoS) . Além disso, a leitura de uma grande quantidade de conteúdo na memória como cadeias de caracteres ou matrizes de bytes pode resultar em alocações que não colaboram bem com o coletor de lixo, causando penalizações adicionais de desempenho.

Considere as seguintes orientações ao desenvolver código que transfere uma grande quantidade de dados:

  • Aproveite o suporte de interoperabilidade no streaming nativo JS para transferir dados maiores do que o limite de tamanho das mensagens recebidas de SignalR.
  • Dicas gerais:
    • Não aloque objetos grandes em código JS e C#.
    • Libere a memória consumida quando o processo for concluído ou cancelado.
    • Impor os seguintes requisitos adicionais para fins de segurança:
      • Declare o tamanho máximo de arquivo ou dados que podem ser passados.
      • Declare a taxa mínima de upload do cliente para o servidor.
    • Depois que os dados são recebidos pelo servidor, os dados podem ser:
      • Armazenado temporariamente em um buffer de memória até que todos os segmentos sejam coletados.
      • Consumido imediatamente. Por exemplo, os dados podem ser armazenados imediatamente em um banco de dados ou gravados em disco à medida que cada segmento é recebido.
  • Divida os dados em partes menores e envie os segmentos de dados sequencialmente até que todos os dados sejam recebidos pelo servidor.
  • Não aloque objetos grandes em código JS e C#.
  • Não bloqueie o thread principal da interface do usuário por longos períodos ao enviar ou receber dados.
  • Libere a memória consumida quando o processo for concluído ou cancelado.
  • Impor os seguintes requisitos adicionais para fins de segurança:
    • Declare o tamanho máximo de arquivo ou dados que podem ser passados.
    • Declare a taxa mínima de upload do cliente para o servidor.
  • Depois que os dados são recebidos pelo servidor, os dados podem ser:
    • Armazenado temporariamente em um buffer de memória até que todos os segmentos sejam coletados.
    • Consumido imediatamente. Por exemplo, os dados podem ser armazenados imediatamente em um banco de dados ou gravados em disco à medida que cada segmento é recebido.

Blazor configuração de rota do ponto de extremidade do Hub no lado do servidor

No arquivo Program, chame MapBlazorHub para mapear o BlazorHub para o caminho padrão do aplicativo. O script Blazor (blazor.*.js) aponta automaticamente para o endpoint criado por MapBlazorHub.

Refletir o estado da conexão do lado do servidor na interface do usuário

Se o cliente detetar uma conexão perdida com o servidor, uma interface do usuário padrão será exibida para o usuário enquanto o cliente tenta se reconectar:

A interface do usuário de reconexão padrão.

A interface do usuário de reconexão padrão.

Se a reconexão falhar, o usuário será instruído a tentar novamente ou recarregar a página:

A interface padrão de reintentar.

A interface padrão de reintentar.

Se a reconexão for bem-sucedida, o estado do usuário geralmente é perdido. O código personalizado pode ser adicionado a qualquer componente para salvar e recarregar o estado do usuário em falhas de conexão. Para obter mais informações, consulte ASP.NET Visão geral do gerenciamento de estado principal Blazor e ASP.NET Gerenciamento de estado do lado do servidor principalBlazor.

Para criar elementos da interface do usuário que rastreiam o estado de reconexão, a tabela a seguir descreve:

  • Um conjunto de components-reconnect-* classes CSS (classe CSS coluna) que são definidas ou removidas por Blazor em um(a) elemento com um id de components-reconnect-modal.
  • Um evento components-reconnect-state-changed (colunaEvento) que indica uma alteração no status da reconexão.
Classe CSS Evento Indica...
components-reconnect-show show Uma conexão perdida. O cliente está tentando se reconectar. O modal de reconexão é mostrado.
components-reconnect-hide hide Uma conexão ativa é restabelecida com o servidor. O modelo de reconexão está fechado.
components-reconnect-retrying retrying O cliente está tentando se reconectar.
components-reconnect-failed failed Falha na reconexão, provavelmente devido a uma falha de rede.
components-reconnect-rejected rejected Reconexão rejeitada.

Quando a alteração do estado de reconexão em components-reconnect-state-changed for failed, chame Blazor.reconnect() em JavaScript para tentar a reconexão.

Quando a alteração de estado de reconexão é rejected, o servidor foi alcançado, mas recusou a conexão, e o estado do usuário no servidor é perdido. Para recarregar o aplicativo, chame location.reload() em JavaScript. Esse estado de conexão pode ocorrer quando:

  • Ocorre uma falha no circuito do lado do servidor.
  • O cliente fica desconectado por tempo suficiente para que o servidor abandone o estado do utilizador. As instâncias dos componentes pertencentes ao utilizador são descartadas.
  • O servidor é reiniciado ou o processo de trabalho do aplicativo é reciclado.

O desenvolvedor adiciona um ouvinte de eventos no elemento modal de reconexão para monitorar e reagir a alterações de estado de reconexão, como visto no exemplo a seguir:

const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed", 
  handleReconnectStateChanged);

function handleReconnectStateChanged(event) {
  if (event.detail.state === "show") {
    reconnectModal.showModal();
  } else if (event.detail.state === "hide") {
    reconnectModal.close();
  } else if (event.detail.state === "failed") {
    Blazor.reconnect();
  } else if (event.detail.state === "rejected") {
    location.reload();
  }
}

Um elemento com um id de components-reconnect-max-retries exibe o número máximo de tentativas de reconexão:

<span id="components-reconnect-max-retries"></span>

Um elemento com id igual a components-reconnect-current-attempt mostra a tentativa de reconexão atual.

<span id="components-reconnect-current-attempt"></span>

Um elemento com uma id de components-seconds-to-next-attempt exibe o número de segundos até à próxima tentativa de reconexão.

<span id="components-seconds-to-next-attempt"></span>

O Blazor Web App modelo de projeto inclui um componente (ReconnectModal) com folha de estilo colocalizada e arquivos JavaScript (Components/Layout/ReconnectModal.razor, ReconnectModal.razor.css) que podem ser personalizados conforme necessário. Esses arquivos podem ser examinados na fonte de referência ASP.NET Core ou inspecionando um aplicativo criado a partir do modelo de projeto Blazor Web App. O componente é adicionado ao projeto quando o projeto é criado no Visual Studio com modo de renderização interativo definido como Server ou Auto ou criado com a CLI do .NET com a opção --interactivity server (padrão) ou --interactivity auto.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Para personalizar a interface de utilizador, defina um único elemento com id e components-reconnect-modal no conteúdo do elemento <body>. O exemplo a seguir coloca o elemento no componente App.

App.razor:

Para personalizar a interface de utilizador, defina um único elemento com id e components-reconnect-modal no conteúdo do elemento <body>. O exemplo a seguir coloca o elemento na página host.

Pages/_Host.cshtml:

Para personalizar a interface de utilizador, defina um único elemento com id e components-reconnect-modal no conteúdo do elemento <body>. O exemplo a seguir coloca o elemento na página de layout.

Pages/_Layout.cshtml:

Para personalizar a interface de utilizador, defina um único elemento com id e components-reconnect-modal no conteúdo do elemento <body>. O exemplo a seguir coloca o elemento na página host.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    Connection lost.<br>Attempting to reconnect...
</div>

Observação

Se mais de um elemento com um id de components-reconnect-modal for renderizado pelo aplicativo, somente o primeiro elemento renderizado receberá alterações de classe CSS para exibir ou ocultar o elemento.

Adicione os seguintes estilos CSS à folha de estilos do site.

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
    background-color: white;
    padding: 2rem;
    border-radius: 0.5rem;
    text-align: center;
    box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
    margin: 50px 50px;
    position: fixed;
    top: 0;
    z-index: 10001;
}

A tabela a seguir descreve as classes CSS aplicadas ao elemento components-reconnect-modal pela estrutura Blazor.

Classe CSS Indica...
components-reconnect-show Uma conexão perdida. O cliente está tentando se reconectar. Mostrar o modal.
components-reconnect-hide Uma conexão ativa é restabelecida com o servidor. Ocultar o modal.
components-reconnect-failed Falha na reconexão, provavelmente devido a uma falha de rede. Para tentar a reconexão, chame window.Blazor.reconnect() em JavaScript.
components-reconnect-rejected Reconexão rejeitada. O servidor foi alcançado, mas recusou a conexão, e o estado do usuário no servidor é perdido. Para recarregar o aplicativo, chame location.reload() em JavaScript. Esse estado de conexão pode ocorrer quando:
  • Ocorre uma falha no circuito do lado do servidor.
  • O cliente fica desconectado por tempo suficiente para que o servidor abandone o estado do utilizador. As instâncias dos componentes pertencentes ao utilizador são descartadas.
  • O servidor é reiniciado ou o processo de trabalho do aplicativo é reciclado.

Ajuste o atraso antes que a interface de reconexão apareça definindo a propriedade transition-delay no CSS do site para o elemento modal. O exemplo a seguir define o atraso de transição de 500 ms (padrão) para 1.000 ms (1 segundo).

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

Para exibir a tentativa de reconexão atual, defina um elemento com um id de components-reconnect-current-attempt. Para exibir o número máximo de tentativas de reconexão, defina um elemento com uma id de components-reconnect-max-retries. O exemplo a seguir coloca esses elementos dentro de um elemento modal de tentativa de reconexão seguindo o exemplo anterior.

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

Quando a janela modal de reconexão personalizada aparece, ela exibe o seguinte conteúdo com um contador de tentativas de reconexão:

Houve um problema com a conexão! (Tentativa de reconexão atual: 1 / 8)

Renderização do lado do servidor

Por padrão, os componentes são pré-renderizados no servidor antes que a conexão do cliente com o servidor seja estabelecida. Para obter mais informações, consulte Persistência de estado pré-renderizado do núcleo ASP.NETBlazor.

Por padrão, os componentes são pré-renderizados no servidor antes que a conexão do cliente com o servidor seja estabelecida. Para obter mais informações, consulte Component Tag Helper no ASP.NET Core.

Monitore a atividade do circuito no lado do servidor

Monitore a atividade do circuito de entrada usando o método CreateInboundActivityHandler em CircuitHandler. A atividade de circuito de entrada é qualquer atividade enviada do navegador para o servidor, como eventos da interface do utilizador ou chamadas de interoperabilidade entre JavaScript e .NET.

Por exemplo, pode usar um manipulador de atividade de circuito para detetar se o cliente está ocioso e registar o seu ID de circuito (Circuit.Id):

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;

public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
    private Circuit? currentCircuit;
    private readonly ILogger logger;
    private readonly Timer timer;

    public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger, 
        IOptions<IdleCircuitOptions> options)
    {
        timer = new Timer
        {
            Interval = options.Value.IdleTimeout.TotalMilliseconds,
            AutoReset = false
        };

        timer.Elapsed += CircuitIdle;
        this.logger = logger;
    }

    private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
    {
        logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        currentCircuit = circuit;

        return Task.CompletedTask;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return context =>
        {
            timer.Stop();
            timer.Start();

            return next(context);
        };
    }

    public void Dispose() => timer.Dispose();
}

public class IdleCircuitOptions
{
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

public static class IdleCircuitHandlerServiceCollectionExtensions
{
    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services, 
        Action<IdleCircuitOptions> configureOptions)
    {
        services.Configure(configureOptions);
        services.AddIdleCircuitHandler();

        return services;
    }

    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitHandler, IdleCircuitHandler>();

        return services;
    }
}

Registe o serviço no ficheiro Program. O exemplo a seguir configura o tempo limite de ociosidade padrão de cinco minutos a cinco segundos para testar a implementação do IdleCircuitHandler anterior:

builder.Services.AddIdleCircuitHandler(options => 
    options.IdleTimeout = TimeSpan.FromSeconds(5));

Os manipuladores de atividade de circuito também fornecem uma abordagem para acessar serviços Blazor de alcance delimitado noutros escopos de injeção de dependência (DI) que não sejamBlazor. Para mais informações e exemplos, consulte:

Blazor arranque

Configure o início manual do circuito de Blazor do SignalRno arquivo App.razor de um Blazor Web App:

Configure o início manual do circuito Blazor do SignalRno ficheiro Pages/_Host.cshtml (Blazor Server):

Configure o início manual do circuito Blazor do SignalRno ficheiro Pages/_Layout.cshtml (Blazor Server):

Configure o início manual do circuito Blazor do SignalRno ficheiro Pages/_Host.cshtml (Blazor Server):

  • Adicione um atributo autostart="false" à etiqueta <script> para o script blazor.*.js.
  • Coloque um script que chame Blazor.start() depois do carregamento do script Blazor e dentro da marca de fechamento </body>.

Quando autostart está desativado, qualquer aspeto do aplicativo que não dependa do circuito funciona normalmente. Por exemplo, o roteamento do lado cliente está funcional. No entanto, qualquer aspeto que dependa do circuito não está operacional até que Blazor.start() seja invocado. O comportamento do aplicativo é imprevisível sem um circuito estabelecido. Por exemplo, os métodos de componentes não são executados enquanto o circuito está desconectado.

Para mais informações, incluindo como inicializar Blazor quando o documento estiver pronto e como encadear para um JS Promise, consulte ASP.NET Core Blazor inicialização.

Configurar tempos limite de SignalR e Keep-Alive no cliente

Configure os seguintes valores para o cliente:

  • withServerTimeout: Configura o tempo limite do servidor em milissegundos. Se esse tempo limite passar sem receber nenhuma mensagem do servidor, a conexão será encerrada com um erro. O valor de tempo limite padrão é de 30 segundos. O tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao intervalo de Keep-Alive (withKeepAliveInterval).
  • withKeepAliveInterval: Configura o intervalo de Keep-Alive em milissegundos (intervalo padrão no qual executar ping no servidor). Essa configuração permite que o servidor detete desconexões rígidas, como quando um cliente desconecta o computador da rede. O ping ocorre no máximo com a mesma frequência com que os pings do servidor. Se o servidor executar pings a cada cinco segundos, atribuir um valor inferior a 5000 faz com que os pings ocorram a cada cinco segundos. O valor padrão é 15 segundos. O intervalo de Keep-Alive deve ser menor ou igual a metade do valor atribuído ao tempo limite do servidor (withServerTimeout).

O exemplo a seguir para o arquivo App.razor (Blazor Web App) mostra a atribuição de valores padrão.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(30000).withKeepAliveInterval(15000);
      }
    }
  });
</script>

O exemplo a seguir para o arquivo Pages/_Host.cshtml (Blazor Server, todas as versões, exceto ASP.NET Core no .NET 6) ou arquivo Pages/_Layout.cshtml (Blazor Server, ASP.NET Core no .NET 6).

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(30000).withKeepAliveInterval(15000);
    }
  });
</script>

No exemplo anterior, o marcador de posição {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do ficheiro. Para obter o local do script e o caminho a ser usado, consulte a estrutura do projeto ASP.NET Core Blazor.

Ao criar uma conexão de hub em um componente, defina o ServerTimeout (padrão: 30 segundos) e KeepAliveInterval (padrão: 15 segundos) no HubConnectionBuilder. Defina o HandshakeTimeout (padrão: 15 segundos) na HubConnectionconstruída. O exemplo a seguir mostra a atribuição de valores padrão:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(30))
        .WithKeepAliveInterval(TimeSpan.FromSeconds(15))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Configure os seguintes valores para o cliente:

  • serverTimeoutInMilliseconds: O tempo de espera do servidor em milissegundos. Se esse tempo limite passar sem receber nenhuma mensagem do servidor, a conexão será encerrada com um erro. O valor de tempo limite padrão é de 30 segundos. O tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao intervalo de Keep-Alive (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: Intervalo padrão no qual executar ping no servidor. Essa configuração permite que o servidor detete desconexões rígidas, como quando um cliente desconecta o computador da rede. O ping ocorre no máximo com a mesma frequência com que os pings do servidor. Se o servidor executar pings a cada cinco segundos, atribuir um valor inferior a 5000 faz com que os pings ocorram a cada cinco segundos. O valor padrão é 15 segundos. O intervalo de Keep-Alive deve ser menor ou igual a metade do valor atribuído ao tempo limite do servidor (serverTimeoutInMilliseconds).

O exemplo a seguir para o arquivo Pages/_Host.cshtml (Blazor Server, todas as versões, exceto ASP.NET Core no .NET 6) ou arquivo Pages/_Layout.cshtml (Blazor Server, ASP.NET Core no .NET 6):

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

No exemplo anterior, o marcador de posição {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do ficheiro. Para obter o local do script e o caminho a ser usado, consulte a estrutura do projeto ASP.NET Core Blazor.

Ao criar uma ligação de hub num componente, estabeleça o ServerTimeout (padrão: 30 segundos), HandshakeTimeout (padrão: 15 segundos) e KeepAliveInterval (padrão: 15 segundos) no HubConnectionconstruído. O exemplo a seguir mostra a atribuição de valores padrão:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Ao alterar os valores do tempo limite do servidor (ServerTimeout) ou o intervalo de Keep-Alive (KeepAliveInterval):

  • O tempo limite do servidor deve ser pelo menos o dobro do valor atribuído ao intervalo de Keep-Alive.
  • O intervalo de Keep-Alive deve ser menor ou igual a metade do valor atribuído ao tempo limite do servidor.

Para obter mais informações, consulte as seções Falhas globais de implantação e conexão dos seguintes artigos:

Ajuste a contagem de tentativas de reconexão do lado do servidor e o intervalo

Para ajustar a contagem e o intervalo de novas tentativas de reconexão, defina o número de novas tentativas (maxRetries) e o período em milissegundos permitido para cada tentativa de repetição (retryIntervalMilliseconds).

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionOptions: {
        maxRetries: 3,
        retryIntervalMilliseconds: 2000
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionOptions: {
      maxRetries: 3,
      retryIntervalMilliseconds: 2000
    }
  });
</script>

No exemplo anterior, o marcador de posição {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do ficheiro. Para obter o local do script e o caminho a ser usado, consulte a estrutura do projeto ASP.NET Core Blazor.

Quando o usuário navega de volta para um aplicativo com um circuito desconectado, a reconexão é tentada imediatamente, em vez de esperar pela duração do próximo intervalo de reconexão. Esse comportamento procura retomar a conexão o mais rápido possível para o usuário.

O tempo de reconexão padrão usa uma estratégia de recuo calculada. As primeiras várias tentativas de reconexão ocorrem em rápida sucessão antes que os atrasos computados sejam introduzidos entre as tentativas. A lógica padrão para calcular o intervalo de repetição é um detalhe de implementação sujeito a alterações sem aviso prévio, mas você pode encontrar a lógica padrão que a estrutura Blazor usa na função computeDefaultRetryInterval (fonte de referência).

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Trocar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Personalize o comportamento do intervalo de repetição especificando uma função para calcular o intervalo de repetição. No exemplo de recuo exponencial a seguir, o número de tentativas anteriores de reconexão é multiplicado por 1.000 ms para calcular o tempo de espera. Quando a contagem de tentativas anteriores de reconexão (previousAttempts) é maior do que o limite máximo de tentativas (maxRetries), null é atribuído ao intervalo de repetição (retryIntervalMilliseconds) para cessar novas tentativas de reconexão:

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
        previousAttempts >= maxRetries ? null : previousAttempts * 1000
    },
  },
});

Uma alternativa é especificar a sequência exata dos intervalos de repetição. Após o último intervalo de repetição especificado, as tentativas são interrompidas porque a função retryIntervalMilliseconds retorna undefined:

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: 
        Array.prototype.at.bind([0, 1000, 2000, 5000, 10000, 15000, 30000]),
    },
  },
});

Para obter mais informações sobre a inicialização de Blazor, consulte ASP.NET Core Blazor inicialização.

Controle quando a interface de reconexão é exibida.

Controlar quando a interface do usuário de reconexão aparece pode ser útil nas seguintes situações:

  • Um aplicativo implantado frequentemente exibe a UI de reconexão devido a tempos limite de ping causados pela latência da rede interna ou da Internet, e você gostaria de aumentar o intervalo de tempo.
  • Uma aplicação deve informar os utilizadores mais rapidamente que a conexão caiu, e gostaria de diminuir o atraso na notificação.

O tempo de aparecimento da interface do usuário de reconexão é influenciado pelo ajuste do intervalo de Keep-Alive e dos tempos limite no cliente. A interface do usuário de reconexão aparece quando o tempo limite do servidor é atingido no cliente (withServerTimeout, seção Configuração do cliente). No entanto, alterar o valor de withServerTimeout requer alterações em outras configurações de Keep-Alive, tempo limite e handshake descritas nas diretrizes a seguir.

Como recomendações gerais para as orientações que se seguem:

  • O intervalo de Keep-Alive deve corresponder entre as configurações de cliente e servidor.
  • Os tempos limite devem ser pelo menos o dobro do valor atribuído ao intervalo de Keep-Alive.

Configuração do servidor

Defina o seguinte:

  • ClientTimeoutInterval (padrão: 30 segundos): A janela de tempo que os clientes precisam enviar uma mensagem antes que o servidor feche a conexão.
  • HandshakeTimeout (padrão: 15 segundos): o intervalo usado pelo servidor para expirar o tempo das solicitações de handshake recebidas de clientes.
  • KeepAliveInterval (padrão: 15 segundos): o intervalo usado pelo servidor para enviar sinais de 'keep alive' para clientes conectados. Observe que também há uma configuração de intervalo de Keep-Alive no cliente, que deve corresponder ao valor do servidor.

A ClientTimeoutInterval e a HandshakeTimeout podem ser aumentadas e o KeepAliveInterval pode permanecer o mesmo. A consideração importante é que, se você alterar os valores, certifique-se de que os tempos limite sejam pelo menos o dobro do valor do intervalo de Keep-Alive e que o intervalo de Keep-Alive corresponda entre servidor e cliente. Para mais informações, consulte a seção Configurar tempos limite e Keep-Alive no clienteSignalR.

No exemplo a seguir:

  • O ClientTimeoutInterval é aumentado para 60 segundos (valor padrão: 30 segundos).
  • O HandshakeTimeout é aumentado para 30 segundos (valor padrão: 15 segundos).
  • O KeepAliveInterval não está definido no código do desenvolvedor e usa seu valor padrão de 15 segundos. Diminuir o valor do intervalo de Keep-Alive aumenta a frequência dos pings de comunicação, o que aumenta a carga no aplicativo, no servidor e na rede. É importante evitar a introdução de um mau desempenho ao baixar o intervalo de Keep-Alive.

Blazor Web App (.NET 8 ou posterior) no arquivo Program do projeto de servidor:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options =>
{
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
    options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Blazor Server no arquivo Program:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
    });

Para obter mais informações, consulte a seção "Opções do manipulador de circuito do lado do servidor" .

Configuração do cliente

Defina o seguinte:

  • withServerTimeout (padrão: 30 segundos): Configura o tempo limite do servidor, especificado em milissegundos, para a conexão do hub do circuito.
  • withKeepAliveInterval (padrão: 15 segundos): o intervalo, especificado em milissegundos, no qual a conexão envia Keep-Alive mensagens.

O tempo limite do servidor pode ser aumentado e o intervalo de Keep-Alive pode permanecer o mesmo. A consideração importante é que, se você alterar os valores, certifique-se de que o tempo limite do servidor seja pelo menos o dobro do valor do intervalo de Keep-Alive e que os valores do intervalo de Keep-Alive correspondam entre o servidor e o cliente. Para mais informações, consulte a seção Configurar tempos limite e Keep-Alive no clienteSignalR.

No exemplo seguinte de configuração de inicialização (local do script Blazor), é utilizado um valor personalizado de 60 segundos para o tempo de espera do servidor. O intervalo de Keep-Alive (withKeepAliveInterval) não está definido e usa seu valor padrão de 15 segundos.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(60000);
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000);
    }
  });
</script>

Ao criar uma conexão de hub em um componente, defina o tempo limite do servidor (WithServerTimeout, padrão: 30 segundos) no HubConnectionBuilder. Defina o HandshakeTimeout (padrão: 15 segundos) na HubConnectionconstruída. Confirme se os tempos limite são pelo menos o dobro do intervalo de Keep-Alive (WithKeepAliveInterval/KeepAliveInterval) e se o valor Keep-Alive corresponde entre servidor e cliente.

O exemplo seguinte baseia-se no componente Index no tutorial SignalRBlazor com . O tempo limite do servidor é aumentado para 60 segundos e o tempo limite do handshake é aumentado para 30 segundos. O intervalo de Keep-Alive não está definido e usa seu valor padrão de 15 segundos.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(60))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Defina o seguinte:

  • serverTimeoutInMilliseconds (padrão: 30 segundos): Configura o tempo limite do servidor, especificado em milissegundos, para a conexão do hub do circuito.
  • keepAliveIntervalInMilliseconds (padrão: 15 segundos): o intervalo, especificado em milissegundos, no qual a conexão envia Keep-Alive mensagens.

O tempo limite do servidor pode ser aumentado e o intervalo de Keep-Alive pode permanecer o mesmo. A consideração importante é que, se você alterar os valores, certifique-se de que o tempo limite do servidor seja pelo menos o dobro do valor do intervalo de Keep-Alive e que os valores do intervalo de Keep-Alive correspondam entre o servidor e o cliente. Para mais informações, consulte a seção Configurar tempos limite e Keep-Alive no clienteSignalR.

No exemplo seguinte de configuração de inicialização (local do script Blazor), é utilizado um valor personalizado de 60 segundos para o tempo de espera do servidor. O intervalo de Keep-Alive (keepAliveIntervalInMilliseconds) não está definido e usa seu valor padrão de 15 segundos.

Em Pages/_Host.cshtml:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 60000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

Ao criar uma ligação de hub num componente, defina o ServerTimeout (padrão: 30 segundos) e HandshakeTimeout (padrão: 15 segundos) na HubConnectioncompilada. Confirme que os tempos limite são pelo menos o dobro do intervalo de Keep-Alive. Confirme se o intervalo de Keep-Alive corresponde entre o servidor e o cliente.

O exemplo seguinte baseia-se no componente Index no tutorial SignalRBlazor com . O ServerTimeout é aumentado para 60 segundos e o HandshakeTimeout é aumentado para 30 segundos. O intervalo de Keep-Alive (KeepAliveInterval) não está definido e usa seu valor padrão de 15 segundos.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Modificar o manipulador de reconexão do lado do servidor

Os eventos de conexão de circuito do manipulador de reconexão podem ser modificados para comportamentos personalizados, como:

  • Para notificar o usuário se a conexão for interrompida.
  • Para realizar o log (a partir do cliente) quando um circuito está conectado.

Para modificar os eventos de conexão, registre retornos de chamada para as seguintes alterações de conexão:

  • As conexões descartadas usam onConnectionDown.
  • Conexões estabelecidas/restabelecidas usam onConnectionUp.

Tanto onConnectionDown como onConnectionUp devem ser especificados.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: (options, error) => console.error(error),
        onConnectionUp: () => console.log("Up, up, and away!")
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: (options, error) => console.error(error),
      onConnectionUp: () => console.log("Up, up, and away!")
    }
  });
</script>

No exemplo anterior, o marcador de posição {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do ficheiro. Para obter o local do script e o caminho a ser usado, consulte a estrutura do projeto ASP.NET Core Blazor.

Controle programático do comportamento de reconexão e recarga

Blazor tenta automaticamente a reconexão e atualiza o navegador quando a reconexão falha. Para obter mais informações, consulte a seção Ajustar a contagem de tentativas de reconexão do lado do servidor e o intervalo . No entanto, o código do desenvolvedor pode implementar um manipulador de reconexão personalizado para assumir o controle total do comportamento de reconexão.

O comportamento de reconexão padrão exige que o usuário execute uma ação manual para atualizar a página após a falha de reconexão. No entanto, o código do desenvolvedor pode implementar um manipulador de reconexão personalizado para assumir o controle total do comportamento de reconexão, incluindo a implementação da atualização automática de página após as tentativas de reconexão falharem.

App.razor:

Pages/_Host.cshtml:

<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>

No exemplo anterior, o marcador de posição {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do ficheiro. Para obter o local do script e o caminho a ser usado, consulte a estrutura do projeto ASP.NET Core Blazor.

Crie o seguinte arquivo de wwwroot/boot.js.

Blazor Web App:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
        onConnectionUp: () => {
          currentReconnectionProcess?.cancel();
          currentReconnectionProcess = null;
        }
      }
    }
  });
})();

Blazor Server:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
      onConnectionUp: () => {
        currentReconnectionProcess?.cancel();
        currentReconnectionProcess = null;
      }
    }
  });
})();

Para obter mais informações sobre a inicialização de Blazor, consulte ASP.NET Core Blazor inicialização.

Desconecte o circuito Blazor do cliente de SignalR

O circuito de Blazor de SignalRé desconectado quando o evento de página unload é acionado. Para desconectar o circuito para outros cenários no cliente, invoque Blazor.disconnect no manipulador de eventos apropriado. No exemplo a seguir, o circuito é desconectado quando a página está oculta (pagehide evento):

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

Para obter mais informações sobre a inicialização de Blazor, consulte ASP.NET Core Blazor inicialização.

Manipulador de circuitos do lado do servidor

Você pode definir um manipulador de circuito , que permite executar código em alterações no estado do circuito de um usuário. Um manipulador de circuitos é implementado derivando de CircuitHandler e registrando a classe no contêiner de serviço do aplicativo. O exemplo a seguir de um manipulador de circuito rastreia conexões SignalR abertas.

TrackingCircuitHandler.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Os manipuladores de circuitos são registrados usando DI. As instâncias com escopo são criadas para cada instância de um circuito. Usando o TrackingCircuitHandler no exemplo anterior, um serviço singleton é criado porque é necessário acompanhar o estado de todos os circuitos.

No ficheiro Program:

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Em Startup.ConfigureServices de Startup.cs:

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Se os métodos de um manipulador de circuito personalizado lançarem uma exceção não tratada, a exceção será fatal para o circuito. Para tolerar exceções no código de um manipulador ou métodos chamados, envolva o código numa ou mais instruções try-catch com manipulação de erros e registo.

Quando um circuito termina porque um utilizador se desconectou e o framework está limpando o estado do circuito, o framework descarta o escopo DI do circuito. A eliminação do escopo descarta todos os serviços de DI com escopo de circuito que implementam System.IDisposable. Se qualquer serviço DI lançar uma exceção não tratada durante a eliminação, o framework registará a exceção. Para obter mais informações, consulte injeção de dependência do ASP.NET CoreBlazor.

Manipulador de circuito do lado do servidor para capturar utilizadores para oferecer serviços personalizados

Use um CircuitHandler para capturar um usuário do AuthenticationStateProvider e definir esse usuário em um serviço. Para obter mais informações e código de exemplo, consulte ASP.NET Core do lado do servidor e Blazor Web App cenários de segurança adicionais.

Fechamento de circuitos quando não há componentes restantes do Servidor Interativo

Os componentes do Servidor Interativo lidam com eventos da interface do usuário da Web usando uma conexão em tempo real com o navegador chamada circuito. Um circuito e seu estado associado são criados quando um componente raiz do Servidor Interativo é renderizado. O circuito é fechado quando não há componentes restantes do Servidor Interativo na página, o que libera recursos do servidor.

Inicie o circuito SignalR em um URL diferente

Impeça a inicialização automática do aplicativo adicionando autostart="false" à tag Blazor<script> (local do script de início Blazor). Estabeleça manualmente a URL do circuito usando Blazor.start. Os exemplos a seguir usam o caminho /signalr.

Blazor Web Apps:

- <script src="_framework/blazor.web.js"></script>
+ <script src="_framework/blazor.web.js" autostart="false"></script>
+ <script>
+   Blazor.start({
+     circuit: {
+       configureSignalR: builder => builder.withUrl("/signalr")
+     },
+   });
+ </script>

Blazor Server:

- <script src="_framework/blazor.server.js"></script>
+ <script src="_framework/blazor.server.js" autostart="false"></script>
+ <script>
+   Blazor.start({
+     configureSignalR: builder => builder.withUrl("/signalr")
+   });
+ </script>

Adicione a seguinte chamada MapBlazorHub com o caminho do hub ao pipeline de processamento de middleware no ficheiro Program da aplicação de servidor.

Blazor Web Apps:

app.MapBlazorHub("/signalr");

Blazor Server:

Deixe a chamada existente para MapBlazorHub no arquivo e adicione uma nova chamada para MapBlazorHub com o caminho:

app.MapBlazorHub();
+ app.MapBlazorHub("/signalr");

Personificação para a autenticação do Windows

As conexões de hub autenticadas (HubConnection) são criadas com UseDefaultCredentials para indicar o uso de credenciais padrão para solicitações HTTP. Para obter mais informações, consulte Autenticação e autorização no ASP.NET Core SignalR.

Quando o aplicativo está sendo executado no IIS Express como o usuário conectado em Autenticação do Windows, que provavelmente é a conta pessoal ou profissional do usuário, as credenciais padrão são as do usuário conectado.

Quando o aplicativo é publicado no IIS, ele é executado sob o Pool de Aplicativos Identity. O HubConnection se conecta como a conta de "usuário" do IIS que hospeda o aplicativo, não como o usuário que acessa a página.

Implemente a representação com o HubConnection para usar a identidade do utilizador ao navegar.

No exemplo a seguir:

protected override async Task OnInitializedAsync()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();

    if (authState?.User.Identity is not null)
    {
        var user = authState.User.Identity as WindowsIdentity;

        if (user is not null)
        {
            await WindowsIdentity.RunImpersonatedAsync(user.AccessToken, 
                async () =>
                {
                    hubConnection = new HubConnectionBuilder()
                        .WithUrl(NavManager.ToAbsoluteUri("/hub"), config =>
                        {
                            config.UseDefaultCredentials = true;
                        })
                        .WithAutomaticReconnect()
                        .Build();

                        hubConnection.On<string>("name", userName =>
                        {
                            name = userName;
                            InvokeAsync(StateHasChanged);
                        });

                        await hubConnection.StartAsync();
                });
        }
    }
}

No código anterior, NavManager é um NavigationManagere AuthenticationStateProvider é uma instância de serviço AuthenticationStateProvider (AuthenticationStateProvider documentação).

Recursos adicionais do lado do servidor