Compartilhar via


Diretrizes do ASP.NET Core BlazorSignalR

Observação

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

Aviso

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

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

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 diretrizes gerais sobre a configuração do ASP.NET Core SignalR, consulte os tópicos na Visão Geral do ASP.NET Core SignalR na área de documentação, especialmente configuração do ASP.NET Core SignalR.

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

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

Serviço SignalR do Azure, com reconexão com estado

A reconexão com estado (WithStatefulReconnect) foi lançada com o .NET 8, mas atualmente não tem suporte para o Serviço SignalR do Azure. Para obter mais informações, veja Suporte para reconexão com estado? (Azure/azure-signalr #1878).

Compactação WebSocket para componentes do Servidor Interativo

Por padrão, os componentes do Servidor Interativo:

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

  • Adote uma diretiva de frame-ancestors CSP (política de segurança de conteúdo) definida como 'self', que só permite incorporar o aplicativo em uma <iframe> da origem, a partir da qual o aplicativo é disponibilizado quando a compactação é habilitada ou quando uma configuração para o contexto do WebSocket é fornecida. A ContentSecurityFrameAncestorPolicy controla a CSP de frame-ancestors.

A CSP de frame-ancestors pode ser removida manualmente definindo o valor de ContentSecurityFrameAncestorsPolicy como null, já que você talvez queira configurar a CSP de maneira centralizada. Quando a CSP de frame-ancestors é gerenciada de maneira centralizada, é necessário ter cuidado ao aplicar uma política sempre que o primeiro documento for renderizado. Não recomendamos remover a política completamente, já que isso pode tornar o aplicativo vulnerável a ataques.

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

Exemplos de uso:

Desabilite a compactação definindo as DisableWebSocketCompression como true, o que reduz a vulnerabilidade do aplicativo aos ataques mas pode resultar em um desempenho reduzido:

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

Quando a compactação estiver habilitada, configure uma CSP de frame-ancestors mais rigorosa com um valor de 'none' (as aspas simples são obrigatórias), que permite a compactação do WebSocket, mas impede que os navegadores incorporem o aplicativo em qualquer <iframe>:

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

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

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

Importante

Os navegadores aplicam as diretivas de CSP de vários cabeçalhos de CSP usando o valor de diretiva de política mais rigoroso. Portanto, um desenvolvedor não pode adicionar uma política de frame-ancestors mais fraca do que 'self' intencionalmente ou por engano.

As aspas simples são obrigatórias no valor da cadeia de caracteres transmitido para a ContentSecurityFrameAncestorsPolicy:

Valores sem suporte: none, self

Valores com suporte: 'none', 'self'

Opções adicionais incluem especificar uma ou mais origens de host e fontes de esquema.

Para obter informações sobre implicações de segurança, confira Diretrizes de mitigação de ameaças para a renderização interativa do Blazor do ASP.NET Core do lado do servidor. Para obter mais informações sobre a diretiva de frame-ancestors, confira CSP: frame-ancestors (documentação de MDN).

Desabilitar a compactação de resposta para Recarga Dinâmica

Desabilite o Middleware de Compactação de Resposta no ambiente ao usar a Recarga DinâmicaDevelopment. Se o código padrão de um modelo de projeto for usado ou não, sempre chame UseResponseCompression primeiro no pipeline de processamento de solicitação.

No arquivo Program:

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

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

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

Use SetBrowserRequestCredentials para definir Include em solicitações fetch entre origens.

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);
    }
}

Quando uma conexão de hub for criada, atribua a 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 por meio de uma cadeia de caracteres, por exemplo https://signalr.example.com, ou por meio de configuração. Navigation é um injetado NavigationManager.

Para obter mais informações, consulte Configuração no ASP.NET Core SignalR.

Renderização do lado do cliente

Se a pré-renderização estiver configurada, ela ocorrerá antes que a conexão do cliente com o servidor seja estabelecida. Para mais informações, confira Pré-renderizar componentes Razor do ASP.NET Core.

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

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

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

  • O circuito SignalR falha ao inicializar com um erro no cliente: Circuit host not initialized.
  • A interface do usuário de reconexão no cliente aparece quando o circuito falha. Não foi possível recuperar.

Para resolver o problema, use ou uma das seguintes abordagens:

  • Reduza a quantidade de dados que você está colocando no estado pré-renderizado.
  • Aumente o limite daSignalR mensagem. 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 temporárias) para hospedagem de webfarm do lado do servidor

Quando mais de um servidor back-end estiver em uso, o aplicativo implementará a afinidade de sessão, também chamada de sessões temporárias. A afinidade de sessão garante que o circuito de um cliente se reconecte ao mesmo servidor se a conexão for descartada, o que é importante porque o estado do cliente é mantido apenas na memória do servidor que estabeleceu o circuito do cliente pela primeira vez.

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

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

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

Serviço SignalR do Azure

O Serviço SignalR do Azure funciona em conjunto com o hub SignalR do aplicativo para escalar verticalmente um aplicativo 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 a reduzir a latência devido à geografia.

O serviço não é necessário para aplicativos Blazor 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 facilitar a escala horinzontal da conexão.
  • Habilitar distribuição global

Para obter mais informações, consulte Hospedar e implantar aplicativos do ASP.NET Core no lado do servidorBlazor.

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

Configure o circuito com CircuitOptions. Exibir 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 o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira 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 arquivo 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 arquivo Program:

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

Leia ou defina as opções em Startup.ConfigureServices 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 Startup.ConfigureServices do Startup.cs:

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

Para configurar o HubConnectionContext, use HubConnectionContextOptions com AddHubOptions. Exiba os padrões para as opções de contexto de conexão do hub na fonte de referência. Para obter descrições de opção na documentação SignalR, confira Configuração do ASP.NET Core SignalR. 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 o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

No arquivo Program:

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

No arquivo Program:

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

No Startup.ConfigureServices do Startup.cs:

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

Aviso

O valor padrão de MaximumReceiveMessageSize é 32. O aumento do valor pode aumentar o risco de ataques de negação de serviço.

Blazor depende do MaximumParallelInvocationsPerClient definido como 1, que é o valor padrão. Para obter mais informações, consulte MaximumParallelInvocationsPerClient > 1 quebra o upload de arquivo no modo Blazor Server (dotnet/aspnetcore nº 53951).

Para obter informações sobre o gerenciamento de memória, consulte Hospedar e implantar Blazoraplicativos do ASP.NET Core no lado do servidor.

Blazor opções de hub

Configure as opções MapBlazorHub para controlar o HttpConnectionDispatcherOptions do hub Blazor. Exiba os padrões para as opções do dispatcher de conexão do hub na 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 o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Faça a chamada para depois da chamada para app.MapBlazorHub app.MapRazorComponents no arquivo do Program aplicativo:

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

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

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

Para contornar o problema de aplicativos direcionados ao .NET 8, dê ao hub Blazor configurado de forma personalizada maior prioridade usando o método WithOrder:

app.MapBlazorHub(options =>
{
    options.CloseOnAuthenticationExpiration = true;
}).WithOrder(-1);

Para saber mais, consulte os recursos a seguir:

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

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

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

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

Tamanho máximo da mensagem de recebimento

Esta seção só se aplica a projetos que implementam o SignalR.

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

Quando o log SignalR não está definido como Depuração ou Rastreamento, um erro de tamanho de mensagem é exibido apenas no console de ferramentas para desenvolvedores do navegador:

Erro: conexão perdida com 'Erro: o servidor retornou um erro ao fechar: conexão fechada com um erro.'.

Quando log do lado do servidor SignalR é definido como Depuração ou Rastreamento, o log do lado do servidor exibe 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 recebida como 64 KB:

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

O aumento do limite de tamanho da mensagem de entrada do SignalR significa sacrificar mais recursos do servidor e eleva o risco de ataques de negação de serviço. Além disso, ler um conteúdo maior na memória como cadeias de caracteres ou matrizes de bytes também pode resultar em alocações que funcionam incorretamente com o coletor de lixo, resultando em penalidades de desempenho adicionais.

Uma boa opção para ler grandes conteúdos é enviar o conteúdo em partes menores e processá-lo como um Stream. Isso pode ser usado ao ler grandes conteúdos JSON de interoperabilidade JavaScript (JS) ou se os dados de interoperabilidade JS estiverem disponíveis como bytes brutos. Para obter um exemplo que demonstra o envio de cargas binárias grandes em aplicativos do lado do servidor que usa técnicas semelhantes ao componente InputFile, confira o aplicativo de exemplo Envio Binário e o BlazorInputLargeTextAreaExemplo de Componente.

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Formulários que processam grande conteúdo acima de SignalR também podem usar a interoperabilidade JS de streaming diretamente. Para obter mais informações, consulte Chamar métodos do .NET das funções JavaScript no ASP.NET Core Blazor. Para obter um exemplo de formulários que transmitem dados <textarea> para o servidor, confira Solucionar problemas de 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 recebida como 64 KB:

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

O aumento do limite de tamanho da mensagem de entrada do SignalR significa sacrificar mais recursos do servidor e eleva o risco de ataques de negação de serviço. Além disso, ler um conteúdo maior na memória como cadeias de caracteres ou matrizes de bytes também pode resultar em alocações que funcionam incorretamente com o coletor de lixo, resultando em penalidades de desempenho adicionais.

Uma boa opção para ler grandes conteúdos é enviar o conteúdo em partes menores e processá-lo como um Stream. Isso pode ser usado ao ler grandes conteúdos JSON de interoperabilidade JavaScript (JS) ou se os dados de interoperabilidade JS estiverem disponíveis como bytes brutos. Para obter um exemplo que demonstra o envio de conteúdos binários grandes no Blazor Server que usa técnicas semelhantes ao componente InputFile, confira o aplicativo de exemplo Envio Binário e o Exemplo de Componente BlazorInputLargeTextArea do .

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Formulários que processam grande conteúdo acima de SignalR também podem usar a interoperabilidade JS de streaming diretamente. Para obter mais informações, consulte Chamar métodos do .NET das funções JavaScript no ASP.NET Core Blazor. Para obter um exemplo de formulários que transmitem dados <textarea> em um aplicativo Blazor Server, confira 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);

O aumento do limite de tamanho da mensagem de entrada do SignalR significa sacrificar mais recursos do servidor e eleva o risco de ataques de negação de serviço. Além disso, ler um conteúdo maior na memória como cadeias de caracteres ou matrizes de bytes também pode resultar em alocações que funcionam incorretamente com o coletor de lixo, resultando em penalidades de desempenho adicionais.

Considere as diretrizes a seguir ao desenvolver um código que transfere um grande volume de dados:

  • Aproveite o suporte à interoperabilidade JS de streaming nativo para transferir dados maiores que o limite de tamanho da mensagem de entrada do SignalR:
  • Dicas gerais:
    • Não aloque objetos grandes no JS e no código C#.
    • Memória consumida liberada quando o processo é concluído ou cancelado.
    • Imponha os seguintes requisitos adicionais para fins de segurança:
      • Declare o tamanho máximo de arquivos 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:
      • Armazenados temporariamente em um buffer de memória até que todos os segmentos sejam coletados.
      • Consumidos 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 no JS e no código C#.
  • Não bloqueie o thread de interface do usuário main por longos períodos ao enviar ou receber dados.
  • Memória consumida liberada quando o processo é concluído ou cancelado.
  • Imponha os seguintes requisitos adicionais para fins de segurança:
    • Declare o tamanho máximo de arquivos 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:
    • Armazenados temporariamente em um buffer de memória até que todos os segmentos sejam coletados.
    • Consumidos imediatamente. Por exemplo, os dados podem ser armazenados imediatamente em um banco de dados ou gravados em disco, à medida que cada segmento é recebido.

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

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 ponto de extremidade criado por MapBlazorHub.

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

Se o cliente detectar 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 do usuário de repetição padrão.

A interface do usuário de repetição padrão.

Se a reconexão for bem-sucedida, o estado do usuário geralmente será 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, confira Gerenciamento de estado Blazor do ASP.NET Core.

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

App.razor:

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

Pages/_Host.cshtml:

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

Pages/_Layout.cshtml:

Para personalizar a interface do usuário, defina um único elemento com um id de no conteúdo do components-reconnect-modal <body> elemento. O exemplo a seguir coloca o elemento na página do 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 do 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 estilos CSS a seguir à 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. Mostra o modal.
components-reconnect-hide Uma conexão ativa é restabelecida para o servidor. Oculta o modal.
components-reconnect-failed Falha na reconexão, provavelmente devido a uma falha de rede. Para tentar reconexão, chame window.Blazor.reconnect() no JavaScript.
components-reconnect-rejected Reconexão rejeitada. O servidor foi atingido, mas recusou a conexão e o estado do usuário no servidor foi perdido. Para recarregar o aplicativo, chame location.reload() no JavaScript. Esse estado de conexão pode resultar quando:
  • Ocorre uma falha no circuito do lado do servidor.
  • O cliente é desconectado tempo suficiente para que o servidor desative o estado do usuário. As instâncias dos componentes do usuário são descartadas.
  • O servidor é reiniciado ou o processo de trabalho do aplicativo é reciclado.

Personalize o atraso antes que a interface de reconexão apareça configurando 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 repetições de reconexão, defina um elemento com um 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 o modal de reconexão personalizado é exibido, ele renderiza o seguinte conteúdo com um contador de tentativa 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 antes que a conexão do cliente com o servidor seja estabelecida. Para mais informações, confira Pré-renderizar componentes Razor do ASP.NET Core.

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

Monitorar a atividade do circuito do lado do servidor

Monitorar a atividade do circuito de entrada usando o método CreateInboundActivityHandler em CircuitHandler. Uma atividade de circuito de entrada é qualquer atividade enviada do navegador para o servidor, como eventos de interface do usuário ou chamadas de interoperabilidade JavaScript para o .NET.

Por exemplo, você pode usar um manipulador de atividade de circuito para detectar se o cliente está ocioso e registrar 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;
    }
}

Cadastre o serviço no arquivo Program. O exemplo a seguir configura o tempo limite de inatividade padrão de cinco minutos a cinco segundos para testar a IdleCircuitHandler implementação 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 com escopo de outros escopos não Blazor de DI (injeção de dependência). Para obter mais informações e exemplos, consulte:

Inicialização do Blazor

Configure o início manual do Blazorcircuito do SignalR no App.razor arquivo de um Blazor Web App:

Configure o início manual do Blazorcircuito do SignalR no Pages/_Host.cshtml arquivo (Blazor Server):

Configure o início manual do Blazorcircuito do SignalR no Pages/_Layout.cshtml arquivo (Blazor Server):

Configure o início manual do Blazorcircuito do SignalR no Pages/_Host.cshtml arquivo (Blazor Server):

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

Quando autostart estiver desabilitado, qualquer aspecto do aplicativo que não dependa do circuito funciona normalmente. Quando, por exemplo, o roteamento do lado do cliente estiver operacional. No entanto, qualquer aspecto que dependa do circuito não estará operacional até Blazor.start() que seja chamado. O comportamento do aplicativo é imprevisível sem um circuito estabelecido. Os métodos de componente, por exemplo, não são executados enquanto o circuito estiver desconectado.

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

Configurar tempos-limite SignalR e manter-se ativo 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 padrão do tempo limite é 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 Keep-Alive em milissegundos (intervalo padrão no qual executar ping no servidor). Essa configuração permite que o servidor detecte desconexões físicas, como quando um cliente desconecta o computador da rede. O ping ocorre no máximo com a frequência em que o servidor realiza o ping. Se o servidor executar pings a cada cinco segundos, atribuir um valor menor que 5000 (5 segundos) executará pings a cada cinco segundos. O valor padrão é 15 segundos. O intervalo de Keep Alive deve ser inferior ou igual à 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 em .NET 6) ou Pages/_Layout.cshtml (Blazor Server, ASP.NET Core em .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 espaço reservado {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do arquivo. Para obter o local do script e o caminho a ser usado, confira Estrutura do projeto ASP.NET Blazor.

Ao criar uma conexão de hub em um componente, defina o ServerTimeout (padrão: 30 segundos) e o KeepAliveInterval (padrão: 15 segundos) no HubConnectionBuilder. Defina o HandshakeTimeout (padrão: 15 segundos) no compilado HubConnection. 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 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 padrão do tempo limite é 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 em que um ping no servidor é executado. Essa configuração permite que o servidor detecte desconexões físicas, como quando um cliente desconecta o computador da rede. O ping ocorre no máximo com a frequência em que o servidor realiza o ping. Se o servidor executar pings a cada cinco segundos, atribuir um valor menor que 5000 (5 segundos) executará pings a cada cinco segundos. O valor padrão é 15 segundos. O intervalo de Keep Alive deve ser inferior ou igual à 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 em .NET 6) ou arquivo Pages/_Layout.cshtml (Blazor Server, ASP.NET Core em .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 espaço reservado {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do arquivo. Para obter o local do script e o caminho a ser usado, confira Estrutura do projeto ASP.NET Blazor.

Ao criar uma conexão de hub em um componente, defina o ServerTimeout (padrão: 30 segundos), HandshakeTimeout (padrão: 15 segundos) e KeepAliveInterval (padrão: 15 segundos) no compilado HubConnection. 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 do 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 inferior ou igual à 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:

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, tais como:

  • Notificar o usuário se a conexão for descartada.
  • Executar o registro em log (do cliente) quando um circuito estiver conectado.

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

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

Ambos onConnectionDown e 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 espaço reservado {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do arquivo. Para obter o local do script e o caminho a ser usado, confira Estrutura do projeto ASP.NET Blazor.

Atualize automaticamente a página quando a reconexão do lado do servidor falhar

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 na reconexão. No entanto, um manipulador de reconexão personalizado pode ser usado para atualizar automaticamente a página:

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 espaço reservado {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do arquivo. Para obter o local do script e o caminho a ser usado, confira Estrutura do projeto ASP.NET Blazor.

Crie o seguinte arquivo 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, veja Inicialização de Blazor no ASP.NET Core.

Ajustar a contagem e o intervalo de repetição de reconexão do lado do servidor

Para ajustar a contagem e o intervalo de repetição de reconexão, defina o número de repetições (maxRetries) e o período em milissegundos permitidos 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 espaço reservado {BLAZOR SCRIPT} é o caminho de script Blazor e o nome do arquivo. Para obter o local do script e o caminho a ser usado, confira Estrutura do projeto ASP.NET 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 busca retomar a conexão o mais rápido possível para o usuário.

O tempo padrão de reconexão usa uma estratégia de retirada computada. As primeiras 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 o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira 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 nova tentativa especificando uma função para calcular o intervalo da repetição. No exemplo de retirada exponencial a seguir, o número de tentativas de reconexão anteriores é multiplicado por 1.000 ms para calcular o intervalo da repetição. Quando a contagem de tentativas anteriores de reconexão (previousAttempts) é maior que o limite máximo de tentativas de repetição (maxRetries), null é atribuído ao intervalo de tentativas de repetição (retryIntervalMilliseconds) para interromper outras tentativas de reconexão:

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

Uma alternativa é especificar a sequência exata de intervalos de novas tentativas. Após o último intervalo de tentativas 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, veja Inicialização de Blazor no ASP.NET Core.

Controle quando a interface do usuário de reconexão é exibida

O controle de quando a interface do usuário de reconexão é exibida pode ser útil nas seguintes situações:

  • Um aplicativo implantado exibe frequentemente a interface do usuário 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 atraso.
  • Um aplicativo deve informar aos usuários que a conexão caiu mais cedo e você gostaria de reduzir o atraso.

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

Como recomendações gerais para as diretrizes a seguir:

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

Configuração de Servidor

Defina o seguinte:

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

O ClientTimeoutInterval e o HandshakeTimeout podem ser aumentados e o KeepAliveInterval pode permanecer o mesmo. A consideração importante é que, se você alterar os valores, verifique se os tempos limite são pelo menos o dobro do valor do intervalo Keep-Alive e se o intervalo Keep-Alive corresponde entre o servidor e o cliente. Para obter mais informações, consulte a seção Configurar SignalR tempos limite e Keep-Alive no cliente.

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 Keep-Alive aumenta a frequência de pings de comunicação, o que aumenta a carga no aplicativo, servidor e rede. Deve-se tomar cuidado para evitar a introdução de um desempenho ruim ao reduzir o intervalo Keep-Alive.

Blazor Web App (.NET 8 ou posterior) no arquivo Program do projeto do 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 mensagens Keep-Alive.

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

No exemplo de configuração de inicialização a seguir (localização do Blazor script), um valor personalizado de 60 segundos é usado para o tempo limite do servidor. O intervalo 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) no compilado HubConnection. Confirme se os tempos limite são pelo menos o dobro do intervalo Keep-Alive (WithKeepAliveInterval/KeepAliveInterval) e se o valor Keep-Alive corresponde entre o servidor e o cliente.

O exemplo a seguir baseia-se no Index componente no SignalR com o tutorial do Blazor. O tempo limite do servidor é aumentado para 60 segundos e o tempo limite do handshake é aumentado para 30 segundos. O intervalo 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 mensagens Keep-Alive.

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

No exemplo de configuração de inicialização a seguir (localização do Blazor script), um valor personalizado de 60 segundos é usado para o tempo limite do servidor. O intervalo 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 conexão de hub em um componente, defina o ServerTimeout (padrão: 30 segundos) e o HandshakeTimeout (padrão: 15 segundos) no HubConnection compilado. Confirme se os tempos limite são pelo menos o dobro do intervalo Keep-Alive. Confirme se o intervalo Keep-Alive corresponde entre o servidor e o cliente.

O exemplo a seguir baseia-se no Index componente no SignalR com o tutorial do Blazor. O ServerTimeout é aumentado para 60 segundos e o HandshakeTimeout é aumentado para 30 segundos. O intervalo 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();
}

Desconecte Blazoro circuito do SignalR do cliente

BlazorO circuito do SignalR é desconectado quando o unload evento Page é disparado. 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 ( eventopagehide):

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

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

Manipulador de circuito do lado do servidor

Você pode definir um manipulador de circuito, o que permite a execução de código em alterações no estado do circuito de um usuário. Um manipulador de circuito é implementado derivando do 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 circuito são registrados usando DI. As instâncias com escopo são criadas por instância de um circuito. Usando o TrackingCircuitHandler no exemplo anterior, um serviço singleton é criado porque o estado de todos os circuitos deve ser rastreado.

No arquivo Program:

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

No Startup.ConfigureServices do Startup.cs:

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Se os métodos de um manipulador de circuito personalizado gerarem uma exceção sem tratamento, a exceção será fatal para o circuito. Para tolerar exceções no código de um manipulador ou métodos chamados, encapsule o código em uma ou mais instruções try-catch com tratamento de erros e registro em log.

Quando um circuito termina porque um usuário se desconectou e a estrutura está limpando o estado do circuito, a estrutura descarta o escopo DI do circuito. Descartar o escopo elimina todos os serviços DI com escopo de circuito que implementam System.IDisposable. Se qualquer serviço DI gerar uma exceção sem tratamento durante o descarte, a estrutura registrará a exceção em log. Para saber mais, confira Injeção de dependência do Blazor no ASP.NET Core.

Manipulador de circuito para capturar usuários para 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 Cenários de segurança adicionais e Blazor Web App do lado do servidor Core.

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

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

IHttpContextAccessor/HttpContext em Razor componentes

IHttpContextAccessor deve ser evitado com a renderização interativa porque não há um HttpContext disponível válido.

IHttpContextAccessor pode ser usado para componentes que são renderizados estaticamente no servidor. No entanto, recomendamos evitá-lo, se possível.

HttpContext pode ser usado como um parâmetro em cascata apenas em componentes raiz renderizados estaticamente para tarefas gerais, como inspecionar e modificar cabeçalhos ou outras propriedades no componente App (Components/App.razor). O valor é sempre null para renderização interativa.

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

Para cenários em que o componente HttpContext é necessário em componentes interativos, recomendamos fluir os dados por meio do estado de componente persistente do servidor. Para obter mais informações, consulte ASP.NET Cenários de segurança adicionais e Blazor Web App do lado do servidor Core.

Não use IHttpContextAccessor/HttpContext direta ou indiretamente nos componentes Razor dos aplicativos Blazor do lado do servidor. Os aplicativos Blazor são executados fora do contexto do pipeline ASP.NET Core. Não há garantia de que o HttpContext esteja disponível no IHttpContextAccessor e não há garantia de que o HttpContext mantenha o contexto que iniciou o aplicativo Blazor.

A abordagem recomendada para passar o estado da solicitação para o aplicativo Blazor é por meio de parâmetros de componente raiz durante a renderização inicial do aplicativo. Como alternativa, o aplicativo pode copiar os dados para um serviço com escopo no evento de ciclo de vida de inicialização do componente raiz para uso em todo o aplicativo. Para obter mais informações, consulte ASP.NET Cenários de segurança adicionais e Blazor Web App do lado do servidor Core.

Um aspecto crítico da segurança do lado do servidor Blazor é que o usuário anexado a um determinado circuito pode ser atualizado em algum momento após o circuito Blazor ser estabelecido, mas o IHttpContextAccessor não é atualizado. Para obter mais informações sobre como lidar com essa situação com serviços personalizados, consulte ASP.NET Cenários de segurança adicionais e Blazor Web App do lado do servidor Core.

Recursos adicionais do lado do servidor