Condividi tramite


Ospitare e distribuire app lato Blazor server

Annotazioni

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avvertimento

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto in fase di anteprima che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non fornisce alcuna garanzia, espressa o implicita, in relazione alle informazioni fornite qui.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Questo articolo illustra come ospitare e distribuire app lato Blazor server (Blazor Web Apps e Blazor Server app) usando ASP.NET Core.

Valori di configurazione dell'host

Le app lato Blazor server possono accettare valori di configurazione host generici.

Distribuzione

Usando un modello di hosting lato server, Blazor viene eseguito nel server dall'interno di un'app ASP.NET Core. Gli aggiornamenti dell'interfaccia utente, la gestione degli eventi e le chiamate JavaScript vengono gestiti tramite una SignalR connessione.

È necessario un server Web in grado di ospitare un'app ASP.NET Core. Visual Studio include un modello di progetto app sul lato server. Per altre informazioni sui Blazor modelli di progetto, vedere Blazor del progetto Core.

Pubblicare un'app nella configurazione della versione e distribuire il contenuto della bin/Release/{TARGET FRAMEWORK}/publish cartella, in cui il {TARGET FRAMEWORK} segnaposto è il framework di destinazione.

Scalabilità

Quando si considera la scalabilità di un singolo server (aumento delle prestazioni), è probabile che la memoria disponibile per un'app sia la prima risorsa esaurita dall'app man mano che l'utente richiede un aumento. La memoria disponibile nel server influisce su:

  • Numero di circuiti attivi che un server può supportare.
  • Latenza dell'interfaccia utente nel client.

Per indicazioni sulla creazione di app lato Blazor server sicure e scalabili, vedere le risorse seguenti:

Ogni circuito usa circa 250 KB di memoria per un'app di tipo Hello World minima. Le dimensioni di un circuito dipendono dal codice dell'app e dai requisiti di manutenzione dello stato associati a ogni componente. Ti consigliamo di misurare le richieste di risorse durante lo sviluppo per l'app e l'infrastruttura, ma la baseline seguente può essere un punto di partenza nella pianificazione della destinazione di distribuzione: se prevedi che l'app supporti 5.000 utenti simultanei, valuta la possibilità di budget di almeno 1,3 GB di memoria server per l'app (o circa 273 KB per utente).

Configurazione SignalR

SignalRLe condizioni di hosting e ridimensionamento si applicano alle Blazor app che usano SignalR.

Per altre informazioni sulle app, incluse le linee guida sulla SignalR configurazione, vedere Blazor di baseBlazorSignalR.

Trasporti

Blazor funziona meglio quando si usano WebSocket come SignalR trasporto a causa di una latenza inferiore, maggiore affidabilità e maggiore sicurezza. Il polling lungo viene usato da SignalR quando WebSocket non è disponibile o quando l'app è configurata in modo esplicito per l'uso del polling lungo.

Viene visualizzato un avviso della console se viene utilizzato long polling:

Impossibile connettersi tramite WebSocket, utilizzando il trasporto di fallback di polling lungo. Ciò può essere dovuto a una VPN o a un proxy che blocca la connessione.

Errori di distribuzione e connessione globali

Raccomandazioni per le distribuzioni globali nei data center geografici:

  • Distribuire l'app nelle aree in cui risiede la maggior parte degli utenti.
  • Prendere in considerazione l'aumento della latenza per il traffico in tutti i continenti. Per controllare l'aspetto dell'interfaccia utente di riconnessione, vedere Blazor di baseSignalR.
  • Prendere in considerazione l'uso del SignalR di Azure.

Servizio app di Azure

L'hosting nel servizio app Azure richiede la configurazione per WebSocket e l'affinità di sessione, detta anche affinità ARR (Application Request Routing).

Annotazioni

Un'app Blazor nel servizio app Azure non richiede SignalR di Azure.

Abilitare quanto segue per la registrazione dell'app nel servizio app Azure:

  • WebSocket per consentire il funzionamento del trasporto WebSocket. L'impostazione predefinita è Disattivata.
  • Affinità di sessione per instradare le richieste da un utente alla stessa istanza di servizio app. L'impostazione predefinita è .
  1. Nella portale di Azure passare all'app Web in servizio app s.
  2. Aprire Configurazione delle impostazioni>.
  3. Impostare Web Sockets su .
  4. Verificare che l'affinità di sessione sia impostata su .

Servizio di Azure SignalR

Il servizio di Azure SignalR facoltativo funziona insieme all'hub dell'app SignalR per aumentare le prestazioni di un'app lato server a un numero elevato di connessioni simultanee. Inoltre, la copertura globale del servizio e i data center ad alte prestazioni aiutano significativamente a ridurre la latenza a causa della geografia.

Il servizio non è necessario per Blazor le app ospitate nel servizio app Azure o nelle app di Azure Container, ma può essere utile in altri ambienti di hosting:

  • Per agevolare l'espansione delle connessioni su larga scala.
  • Gestire la distribuzione globale.

Il servizio Azure SignalR con SDK v1.26.1 o versione successiva supporta SignalR riconnessione con stato (WithStatefulReconnect).

Se l'app usa il polling lungo o esegue il fallback a Polling lungo anziché a WebSocket, potrebbe essere necessario configurare l'intervallo di polling massimo (MaxPollIntervalInSecondsimpostazione predefinita: 5 secondi, limite: 1-300 secondi), che definisce l'intervallo di polling massimo consentito per le connessioni di polling lungo nel servizio di Azure SignalR . Se la richiesta di polling successiva non arriva entro l'intervallo massimo di polling, il servizio chiude la connessione client.

Per indicazioni su come aggiungere il servizio come dipendenza a una distribuzione di produzione, vedere Pubblicare un'app ASP.NET Core SignalR per app Azure Servizio.

Per altre informazioni, vedere:

App contenitore di Azure

Per un'esplorazione più approfondita del ridimensionamento delle app lato Blazor server nel servizio App Contenitore di Azure, vedere Ridimensionamento delle app di base ASP.NET in Azure. L'esercitazione illustra come creare e integrare i servizi necessari per ospitare app in App Azure Container. In questa sezione vengono forniti anche i passaggi di base.

  1. Configurare il servizio App Contenitore di Azure per l'affinità di sessione seguendo le indicazioni riportate in Affinità di sessione in App Azure Container (documentazione di Azure).

  2. Il servizio Protezione dati di base (DP) ASP.NET deve essere configurato per rendere persistenti le chiavi in una posizione centralizzata a cui tutte le istanze del contenitore possono accedere. Le chiavi possono essere archiviate in Archiviazione BLOB di Azure e protette con Azure Key Vault. Il servizio Device Provisioning usa le chiavi per deserializzare Razor i componenti. Per configurare il servizio Device Provisioning per l'uso di Archiviazione BLOB di Azure e Azure Key Vault, fare riferimento ai pacchetti NuGet seguenti:

    Annotazioni

    Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Verificare le versioni corrette dei pacchetti in NuGet.org.

  3. Eseguire l'aggiornamento Program.cs con il codice evidenziato seguente:

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

    Le modifiche precedenti consentono all'app di gestire il servizio Device Provisioning usando un'architettura centralizzata e scalabile. DefaultAzureCredential individua l'identità gestita dell'app contenitore dopo che il codice è stato distribuito in Azure e la utilizza per connettersi all'archiviazione BLOB e al key vault dell'app.

  4. Per creare l'identità gestita per l'app contenitore e concedergli l'accesso all'archiviazione BLOB e a un key vault, seguire questa procedura:

    1. Nel portale di Azure passare alla pagina di panoramica dell'app contenitore.
    2. Selezionare Service Connector (Connettore di servizi) nel riquadro di spostamento sinistro.
    3. Selezionare + Crea nella barra di spostamento superiore.
    4. Nel menu a comparsa Crea connessione immettere i valori seguenti:
      • Contenitore: selezionare l'app contenitore creata per ospitare l'app.
      • Tipo di servizio: selezionare Archiviazione BLOB.
      • Sottoscrizione: selezionare la sottoscrizione proprietaria dell'app contenitore.
      • Nome connessione: immettere un nome .scalablerazorstorage
      • Tipo di client: selezionare .NET e quindi selezionare Avanti.
    5. Selezionare Identità gestita assegnata dal sistema e selezionare Avanti.
    6. Usare le impostazioni di rete predefinite e selezionare Avanti.
    7. Dopo che Azure convalida le impostazioni, selezionare Crea.

    Ripetere le impostazioni precedenti per l'insieme di credenziali delle chiavi. Selezionare il servizio e la chiave dell'insieme di credenziali delle chiavi appropriati nella scheda Informazioni di base .

Annotazioni

L'esempio precedente usa DefaultAzureCredential per semplificare l'autenticazione durante lo sviluppo di app distribuite in Azure combinando le credenziali usate negli ambienti di hosting di Azure con le credenziali usate nello sviluppo locale. Quando si passa all'ambiente di produzione, un'alternativa è una scelta migliore, ad esempio ManagedIdentityCredential. Per altre informazioni, vedere Autenticare le app .NET ospitate in Azure nelle risorse di Azure usando un'identità gestita assegnata dal sistema.

IIS

Quando si usa IIS, abilitare:

Per altre informazioni, vedere le linee guida e i collegamenti incrociati delle risorse IIS esterne in Pubblicare un'app ASP.NET Core in IIS.

Kubernetes

Creare una definizione di ingresso con le annotazioni Kubernetes seguenti per l'affinità di sessione:

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 con Nginx

Seguire le indicazioni per un'app SignalR ASP.NET Core con le modifiche seguenti:

  • Modificare il location percorso da /hubroute (location /hubroute { ... }) al percorso / radice (location / { ... }).
  • Rimuovere la configurazione per il buffering proxy (proxy_buffering off;) perché l'impostazione si applica solo agli eventi inviati dal server (SSE), che non sono rilevanti per Blazor le interazioni client-server dell'app.

Per altre informazioni e indicazioni sulla configurazione, vedere le risorse seguenti:

Linux con Apache

Per ospitare un'app Blazor dietro Apache in Linux, configurare ProxyPass per il traffico HTTP e WebSocket.

Nell'esempio seguente:

  • Kestrel server è in esecuzione nel computer host.
  • L'app rimane in ascolto del traffico sulla 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/

Abilitare i moduli seguenti:

a2enmod   proxy
a2enmod   proxy_wstunnel

Controllare la presenza di errori webSocket nella console del browser. Errori di esempio:

  • Firefox non riesce a stabilire una connessione al server all'indirizzo ws://the-domain-name.tld/_blazor?id=XXX
  • Errore: Impossibile avviare il trasporto 'WebSockets': Errore: Errore: Errore: errore con il trasporto.
  • Errore: Impossibile avviare il trasporto 'LongPolling': TypeError: this.transport non è definito
  • Errore: impossibile connettersi al server con uno dei trasporti disponibili. WebSocket non riuscito
  • Errore: impossibile inviare dati se la connessione non è nello stato "Connesso".

Per altre informazioni e indicazioni sulla configurazione, vedere le risorse seguenti:

Misurare la latenza di rete

JS l'interoperabilità può essere usata per misurare la latenza di rete, come illustrato nell'esempio seguente.

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

Per un'esperienza di interfaccia utente ragionevole, è consigliabile una latenza dell'interfaccia utente sostenuta di 250 ms o meno.