Compartilhar via


Hospedar e implantar aplicativos Blazor do lado do servidor

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão do .NET 10 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 do .NET 10 deste artigo.

Esse artigo explica como hospedar e implantar aplicativos Blazor (Blazor Web Apps e Blazor Server) do lado do servidor usando o ASP.NET Core.

Valores de configuração do host

Os aplicativos Blazor do lado do servidor podem aceitar valores de configuração de Host Genérico.

Implantação

Ao usar um modelo de hospedagem no lado do servidor, Blazor é executado no servidor a partir de um aplicativo do ASP.NET Core. As atualizações da interface do usuário, a manipulação de eventos e as chamadas de JavaScript são realizadas por uma conexão SignalR.

É necessário um servidor Web capaz de hospedar um aplicativo ASP.NET Core. O Visual Studio inclui um modelo de projeto de aplicativo do lado do servidor. Para obter mais informações sobre modelos de projeto Blazor, consulte Estrutura do projeto Blazor do ASP.NET Core.

Publique um aplicativo na configuração de versão e implante o conteúdo da pasta bin/Release/{TARGET FRAMEWORK}/publish, em que o espaço reservado {TARGET FRAMEWORK} é a estrutura de destino.

Escalabilidade

Ao considerar a escalabilidade de um único servidor (escalar verticalmente), a memória disponível para um aplicativo provavelmente é o primeiro recurso que o aplicativo esgota à medida que as demandas do usuário aumentam. A memória disponível no servidor afeta:

  • Número de circuitos ativos aos quais um servidor pode dar suporte.
  • Latência de interface do usuário no cliente.

Para obter diretrizes sobre como compilar aplicativos Blazor seguros e escalonáveis do lado do servidor, consulte os recursos a seguir:

Cada circuito usa aproximadamente 250 KB de memória para um aplicativo mínimo de estilo Olá, Mundo. O tamanho de um circuito depende do código do aplicativo e dos requisitos de manutenção de estado associados a cada componente. Recomendamos que você meça as demandas de recursos durante o desenvolvimento para seu aplicativo e infraestrutura, mas a linha de base a seguir pode ser um ponto de partida no planejamento de seu destino de implantação: se você espera que seu aplicativo dê suporte a 5.000 usuários simultâneos, considere o orçamento de pelo menos 1,3 GB de memória do servidor para o aplicativo (ou ~273 KB por usuário).

Pré-carregamento de ativos estáticos do Blazor WebAssembly

O componente ResourcePreloader no conteúdo do cabeçalho do componente App (App.razor) é utilizado para fazer referência a ativos estáticos do Blazor. O componente é colocado após a marca de URL base (<base>):

<ResourcePreloader />

Um Razor componente é usado em vez de <link> elementos porque:

  • O componente permite que a URL base (valor de atributo da tag <base> de href) identifique corretamente o diretório raiz do aplicativo Blazor em um aplicativo ASP.NET Core.
  • O recurso pode ser removido ao remover o marcador ResourcePreloader do componente App. Isso é útil nos casos em que o aplicativo utiliza um retorno de chamada loadBootResource para modificar URLs.

SignalR configuração

As condições de hospedagem e dimensionamento do SignalR's valem para os aplicativos Blazor que usam SignalR.

Para obter mais informações sobre aplicativos SignalR em Blazor, incluindo diretrizes de configuração, consulte ASP.NET Core BlazorSignalR diretrizes.

Transportes

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

Um aviso do console será exibido se a Sondagem Longa for utilizada:

Falha ao se conectar por meio de WebSockets usando o transporte de fallback de Sondagem Longa. Isso pode ocorrer porque uma VPN ou proxy está bloqueando a conexão.

Falhas globais de implantação e conexão

Recomendações para implantações globais nos data centers geográficos:

  • Implante o aplicativo nas regiões em que a maioria dos usuários reside.
  • Leve em consideração o aumento da latência para o tráfego em todos os continentes. Para controlar a aparência da UI de reconexão, veja a orientação do ASP.NET Core BlazorSignalR.
  • Considerar o uso de serviços do AzureSignalR.

Serviço de Aplicativo do Azure

Para hospedar um Serviço de Aplicativo do Azure, é necessário configurar o WebSockets e a afinidade de sessão, também chamada de afinidade ARR (Application Request Routing).

Observação

Um aplicativo Blazor no Serviço de Aplicativo do Azure não exige o Serviço SignalR do Azure.

Para o registro do aplicativo no Serviço de Aplicativo do Azure, ative:

  • WebSockets para permitir que o transporte de WebSockets funcione. A configuração padrão é Off.
  • Afinidade de sessão para encaminhar as solicitações de um usuário de volta para a mesma instância do Serviço de Aplicativo. A configuração padrão é On.
  1. No portal do Azure, navegue até o aplicativo Web emServiços de Aplicativos.
  2. Abra Configurações>Configuração.
  3. Defina os Soquetes da Web como Ativados.
  4. Verifique se a Afinidade de sessão está definida como Ativada.

Serviço SignalR do Azure

O Azure SignalR Service opcional funciona em conjunto com o hub do aplicativo SignalR para dimensionar um aplicativo no 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 é obrigató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 ampliação da conexão.
  • Habilitar distribuição global.

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

Caso o aplicativo use a Sondagem longa ou volte a Sondagem longa em vez de WebSockets, talvez seja preciso configurar o intervalo máximo de sondagem (MaxPollIntervalInSeconds, padrão: 5 segundos, limite: 1-300 segundos), que define o intervalo máximo de sondagem permitido para conexões de Sondagem longa no Serviço do Azure SignalR. Se a próxima solicitação de sondagem não chegar no intervalo máximo de sondagem, o serviço fechará a conexão do cliente.

Para obter diretrizes sobre como adicionar o serviço como uma dependência a uma implantação de produção, consulte Publicar um aplicativo do ASP.NET CoreSignalR no Serviço de Aplicativo do Azure.

Para obter mais informações, consulte:

Aplicativos de Contêiner do Azure

Para ver uma exploração mais detalhada da escala de aplicativos Blazor do lado do servidor no serviço Aplicativos de Contêiner do Azure, confira Como escalar aplicativos ASP.NET Core no Azure. O tutorial explica como criar e integrar os serviços necessários para hospedar aplicativos nos Aplicativos de Contêiner do Azure. As etapas básicas também são fornecidas nesta seção.

  1. Configure o serviço Aplicativos de Contêiner do Azure para a afinidade de sessão seguindo as diretrizes descritas em Afinidade de sessão nos Aplicativos de Contêiner do Azure (documentação do Azure).

  2. O serviço de DP (Proteção de Dados) do ASP.NET Core deve ser configurado para manter as chaves em uma localização centralizada que todas as instâncias de contêiner poderem acessar. As chaves podem ser armazenadas no Armazenamento de Blobs do Azure e protegidas com o Azure Key Vault. O serviço de proteção de dados utiliza as chaves para desserializar componentes Razor. Para configurar o serviço de proteção de dados para usar o Armazenamento de Blobs do Azure e o Azure Key Vault, consulte os seguintes pacotes NuGet:

    Observação

    Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas de pacote em NuGet.org.

  3. Atualize Program.cs com o seguinte código realçado:

    using Azure.Identity;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
    var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];
    
    builder.Services.AddRazorPages();
    builder.Services.AddHttpClient();
    builder.Services.AddServerSideBlazor();
    
    builder.Services.AddAzureClientsCore();
    
    builder.Services.AddDataProtection()
                    .PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
                                                    new DefaultAzureCredential())
                    .ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
                                                    new DefaultAzureCredential());
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapRazorPages();
    
    app.Run();
    

    As alterações anteriores permitem que o aplicativo gerencie a proteção de dados com uma arquitetura centralizada e escalonável. DefaultAzureCredential descobre a identidade gerenciada do aplicativo de contêiner depois que o código é implantado no Azure e o usa para se conectar ao armazenamento de blobs e ao cofre de chaves do aplicativo.

  4. Para criar a identidade gerenciada do aplicativo de contêiner e conceder-lhe acesso ao armazenamento de blobs e a um cofre de chaves, conclua as seguintes etapas:

    1. No Portal do Azure, navegue até a página de visão geral do aplicativo de contêiner.
    2. À esquerda, selecione Conector de serviço.
    3. Selecione + Criar na navegação superior.
    4. No submenu Criar conexão, insira os seguintes valores:
      • Contêiner: selecione o aplicativo de contêiner que você criou para hospedar seu aplicativo.
      • Tipo de serviço: selecione Armazenamento de Blobs.
      • Assinatura: selecione a assinatura que possui o aplicativo de contêiner.
      • Nome da conexão: insira um nome de scalablerazorstorage.
      • Tipo de cliente: selecione .NET e, em seguida, selecione Avançar.
    5. Selecione Identidade gerenciada atribuída pelo sistema e selecione Avançar.
    6. Use as configurações de rede padrão e selecione Avançar.
    7. Depois que o Azure validar as configurações, selecione Criar.

    Repita as configurações anteriores para o cofre de chaves. Selecione o serviço e a chave do cofre de chaves apropriados na guia Básico.

Observação

O exemplo anterior usa DefaultAzureCredential para simplificar a autenticação ao desenvolver aplicativos que serão implantados no Azure, combinando credenciais utilizadas em ambientes de hospedagem do Azure com aquelas utilizadas no desenvolvimento local. Ao migrar para a produção, é melhor escolher uma alternativa, como ManagedIdentityCredential. Para obter mais informações, consulte Autenticar aplicativos .NET hospedados no Azure em recursos do Azure usando uma identidade gerenciada atribuída pelo sistema.

IIS

Ao usar o IIS, habilite:

Para obter mais informações, consulte as diretrizes e links cruzados de recursos externos do IIS em Publicar um aplicativo do ASP.NET Core no IIS.

Kubernetes

Crie uma definição de entrada com as seguintes Anotações do Kubernetes para afinidade de sessão:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: <ingress-name>
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
    nginx.ingress.kubernetes.io/session-cookie-name: "affinity"
    nginx.ingress.kubernetes.io/session-cookie-expires: "14400"
    nginx.ingress.kubernetes.io/session-cookie-max-age: "14400"

Linux utilizando Nginx

Siga as diretrizes para um aplicativo SignalR do ASP.NET Core com as seguintes alterações:

  • Altere o location caminho de /hubroute (location /hubroute { ... }) para o caminho raiz / (location / { ... }).
  • Remova a configuração para buffer de proxy (proxy_buffering off;), pois a configuração só se aplica a SSE (Eventos Enviados pelo Servidor), que não são relevantes para as interações cliente-servidor do aplicativo do Blazor.

Para obter mais informações e diretrizes de configuração, confira os seguintes recursos:

Linux com o Apache

Para hospedar um aplicativo Blazor por trás do Apache no Linux, configure ProxyPass para tráfego HTTP e WebSockets.

No exemplo a seguir:

  • O servidor Kestrel está em execução no computador host.
  • O aplicativo escuta o tráfego na porta 5000.
ProxyPreserveHost   On
ProxyPassMatch      ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass           /_blazor ws://localhost:5000/_blazor
ProxyPass           / http://localhost:5000/
ProxyPassReverse    / http://localhost:5000/

Habilite os seguintes módulos:

a2enmod   proxy
a2enmod   proxy_wstunnel

Verifique se há erros de WebSockets no console do navegador. Erros de exemplo:

  • O Firefox não pode estabelecer uma conexão com o servidor em ws://the-domain-name.tld/_blazor?id=XXX
  • Erro: falha ao iniciar o transporte 'WebSockets': Error: ocorreu um erro com o transporte.
  • Erro: falha ao iniciar o transporte 'LongPolling': TypeError: this.transport é indefinido
  • Erro: não é possível se conectar ao servidor com qualquer um dos transportes disponíveis. Falha no WebSockets
  • Erro: não é possível enviar dados se a conexão não estiver no Estado 'Conectado'.

Para obter mais informações e diretrizes de configuração, confira os seguintes recursos:

Medir latência de rede

A JS interoperabilidade pode ser usada para medir a latência de rede, como demonstra o exemplo a seguir.

MeasureLatency.razor:

@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}
@inject IJSRuntime JS

@if (latency is null)
{
    <span>Calculating...</span>
}
else
{
    <span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
    private DateTime startTime;
    private TimeSpan? latency;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            startTime = DateTime.UtcNow;
            var _ = await JS.InvokeAsync<string>("toString");
            latency = DateTime.UtcNow - startTime;
            StateHasChanged();
        }
    }
}

Para uma experiência de interface do usuário razoável, recomendamos uma latência de interface do usuário sustentada de 250 ms ou menos.