Condividi tramite


Linee guida di ASP.NET Core BlazorSignalR

Nota

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

Avviso

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 non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

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

Questo articolo illustra come configurare e gestire SignalR le connessioni nelle Blazor app.

Per indicazioni generali sulla configurazione di ASP.NET Core SignalR, vedere gli argomenti nell'area Panoramica di ASP.NET Core SignalR della documentazione, in particolare la configurazione di ASP.NET Core SignalR.

Le app lato server usano ASP.NET Core SignalR per comunicare con il browser. SignalRLe condizioni di hosting e ridimensionamento si applicano alle app server.

Blazor funziona meglio quando si usano WebSocket come SignalR trasporto a causa di bassa latenza, affidabilità e sicurezza. Long Polling viene utilizzato da SignalR quando WebSockets non è disponibile o quando l'app è configurata esplicitamente per utilizzarlo.

Servizio di Azure SignalR con riconnessione con stato

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

Compressione WebSocket per componenti server interattivi

Per impostazione predefinita, i componenti di Interactive Server:

  • Abilitare la compressione per le connessioni WebSocket. DisableWebSocketCompression (impostazione predefinita: false) controlla la compressione WebSocket.

  • Adottare una direttiva CSP (Content Security Policy) impostata su frame-ancestors, che è l'impostazione predefinita e consente solo l'incorporamento dell'app in un 'self' dell'origine da cui viene servita l'app, quando la compressione è abilitata o quando viene fornita una configurazione per il contesto WebSocket.

La configurazione predefinita del CSP frame-ancestors può essere modificata impostando il valore di ContentSecurityFrameAncestorsPolicy su null se si desidera configurare il CSP in modo centralizzato o per una politica ancora più rigorosa. Quando il frame-ancestors CSP viene gestito in modo centralizzato, è necessario prestare attenzione ad applicare un criterio ogni volta che viene eseguito il rendering del primo documento. Non è consigliabile rimuovere completamente i criteri, perché rende l'app vulnerabile agli attacchi. Per altre informazioni, vedere Applicare criteri di sicurezza del contenuto per ASP.NET CoreBlazor e la Guida MDN CSP.

Utilizzare ConfigureWebSocketAcceptContext per configurare per WebSocketAcceptContext le connessioni WebSocket usate dai componenti del server. Per impostazione predefinita, viene applicato un criterio che abilita la compressione e imposta un CSP per gli antenati del frame definiti in ContentSecurityFrameAncestorsPolicy.

Esempi di utilizzo:

Disabilitare la compressione impostando DisableWebSocketCompression su true, che riduce la vulnerabilità dell'app per l'attacco , ma può comportare una riduzione delle prestazioni:

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

Quando la compressione è abilitata, configura un CSP più rigoroso con un valore frame-ancestors (sono necessarie virgolette singole), che consente la compressione WebSocket ma impedisce ai browser di incorporare l'app in un 'none' oggetto:

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

Quando la compressione è abilitata, rimuovere il CSP impostando frame-ancestors su ContentSecurityFrameAncestorsPolicy. Questo scenario è consigliato solo per le app che impostano la politica di sicurezza dei contenuti in modo centralizzato:

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

Importante

I browser applicano direttive CSP da più intestazioni CSP utilizzando il criterio di direttiva più rigoroso. Pertanto, uno sviluppatore non può aggiungere un criterio più debole frame-ancestors che 'self' a scopo o per errore.

Le virgolette singole sono obbligatorie per il valore stringa passato a ContentSecurityFrameAncestorsPolicy:

Valori non supportati:none, self

Valori supportati:'none', 'self'

Le opzioni aggiuntive includono la specifica di una o più origini host e una o più origini di schema.

Per le implicazioni relative alla sicurezza, vedere Linee guida per la mitigazione delle minacce per ASP.NET Core nel rendering interattivo lato serverBlazor. Per altre informazioni, vedere Applicare criteri di sicurezza del contenuto per ASP.NET Core Blazor e CSP: frame-ancestors (documentazione mdn).

Disabilitare la compressione delle risposte per Ricaricamento rapido

Quando si utilizza Hot Reload, disabilitare il middleware di compressione della risposta nell'ambiente Development. Indipendentemente dal fatto che venga usato o meno il codice predefinito da un modello di progetto, chiamare UseResponseCompression sempre prima nella pipeline di elaborazione della richiesta.

Nel file Program:

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

Negoziazione cross-origin lato cliente per l'autenticazione

Questa sezione illustra come configurare il client sottostante SignalRper l'invio di credenziali, ad esempio cookie o intestazioni di autenticazione HTTP.

Usare SetBrowserRequestCredentials per impostare Include nelle richieste cross-origin fetch.

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

Dove viene creata una connessione hub, assegnare HttpMessageHandler all'opzione HttpMessageHandlerFactory.

private HubConnectionBuilder? hubConnection;

...

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

L'esempio precedente configura l'indirizzo URI assoluto della connessione hub a /chathub. L'URI può anche essere impostato tramite una stringa, ad esempio https://signalr.example.como tramite la configurazione. Navigation è un oggetto iniettato NavigationManager.

Per altre informazioni, vedere configurazione di ASP.NET CoreSignalR.

Rendering lato client

Se il prerendering è configurato, il prerendering si verifica prima che venga stabilita la connessione del client al server. Per altre informazioni, vedere componenti Prerender ASP.NET CoreRazor.

Se il prerendering è configurato, il prerendering si verifica prima che venga stabilita la connessione del client al server. Per altre informazioni, vedere gli articoli seguenti:

Dimensioni dello stato prerisorse e SignalR limite di dimensioni dei messaggi

Una dimensione dello stato prerenderizzata di grandi dimensioni può superare il limite di dimensione dei messaggi del circuito Blazor, causando il seguente risultato:

  • L'inizializzazione del SignalR circuito non riesce con un errore nel client: Circuit host not initialized.
  • L'interfaccia utente di riconnessione nel client viene visualizzata quando il circuito ha esito negativo. Il ripristino non è possibile.

Per risolvere il problema, usare uno degli approcci seguenti:

  • Ridurre la quantità di dati inseriti nello stato prerenderizzato.
  • Aumentare il limite della dimensione del messaggio. AVVISO: l'aumento del limite può aumentare il rischio di attacchi Denial of Service (DoS).

Risorse aggiuntive sul lato client

Usare l'affinità di sessione (sessioni permanenti) per l'hosting di web farm sul lato server

Quando sono in uso più server back-end, l'app deve implementare l'affinità di sessione, nota anche come sessioni permanenti. L'affinità di sessione garantisce che il circuito di un client si riconnette allo stesso server se la connessione viene interrotta, che è importante perché lo stato del client viene mantenuto solo nella memoria del server che ha stabilito il circuito del client.

L'errore seguente viene generato da un'app che non ha abilitato l'affinità di sessione in una Web farm:

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

Per ulteriori informazioni sull'affinità di sessione con l'hosting di Azure App Service, vedere Host e distribuire le app server-side di ASP.NET CoreBlazor.

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.

Per ulteriori informazioni, consultare Host e distribuire le app lato server di ASP.NET Core .

Opzioni di gestione del circuito lato server

Configurare il circuito con CircuitOptions. Visualizzare i valori predefiniti nell'origine di riferimento.

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Leggi o imposta le opzioni nel file Program utilizzando un delegato delle opzioni per AddInteractiveServerComponents. Il {OPTION} segnaposto rappresenta l'opzione e il {VALUE} segnaposto è il valore .

Nel file Program:

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

Leggi o imposta le opzioni nel file Program utilizzando un delegato delle opzioni per AddServerSideBlazor. Il {OPTION} segnaposto rappresenta l'opzione e il {VALUE} segnaposto è il valore .

Nel file Program:

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

Leggere o impostare le opzioni in Startup.ConfigureServices con un delegato di opzioni su AddServerSideBlazor. Il {OPTION} segnaposto rappresenta l'opzione e il {VALUE} segnaposto è il valore .

In Startup.ConfigureServices di Startup.cs:

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

Per configurare HubConnectionContext, usare HubConnectionContextOptions con AddHubOptions. Visualizza le impostazioni predefinite per le opzioni del contesto di connessione hub nella fonte di riferimento. Per le descrizioni delle opzioni nella SignalR documentazione, vedere configurazione di ASP.NET CoreSignalR. Il {OPTION} segnaposto rappresenta l'opzione e il {VALUE} segnaposto è il valore .

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Nel file Program:

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

Nel file Program:

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

In Startup.ConfigureServices di Startup.cs:

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

Avviso

Il valore predefinito di MaximumReceiveMessageSize è 32 KB. L'aumento del valore può aumentare il rischio di attacchi Denial of Service (DoS).

Blazor si basa su MaximumParallelInvocationsPerClient impostato su 1, che è il valore predefinito. Per altre informazioni, vedere MaximumParallelInvocationsPerClient > 1 interrompe il caricamento dei file in Blazor Server modalità (dotnet/aspnetcore #53951).

Per informazioni sulla gestione della memoria, vedere Gestire la memoria in app ASP.NET Core lato server distribuiteBlazor.

Blazor opzioni hub

Configurare le opzioni MapBlazorHub per controllare HttpConnectionDispatcherOptions del hub Blazor. Consulta le impostazioni predefinite per le opzioni del dispatcher della connessione hub nell'origine di riferimento. Il {OPTION} segnaposto rappresenta l'opzione e il {VALUE} segnaposto è il valore .

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Inserire la chiamata a app.MapBlazorHub dopo la chiamata a app.MapRazorComponents nel file Program dell'app.

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

La configurazione dell'hub usato da AddInteractiveServerRenderMode con MapBlazorHub ha esito negativo a causa di un AmbiguousMatchException.

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

Per risolvere il problema per le app destinate a .NET 8, assegnare una precedenza più alta all'hub personalizzato Blazor configurato utilizzando il metodo WithOrder.

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

Per ulteriori informazioni, vedi le seguenti risorse:

Fornire le opzioni a app.MapBlazorHub nel file dell'app Program :

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

Fornire le opzioni a app.MapBlazorHub nella configurazione del routing degli endpoint:

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

Dimensione massima dei messaggi di ricezione

Questa sezione si applica solo ai progetti che implementano SignalR.

Le dimensioni massime consentite per i messaggi in arrivo SignalR nei metodi hub sono limitate da HubOptions.MaximumReceiveMessageSize (impostazione predefinita: 32 KB). SignalR messaggi più grandi di MaximumReceiveMessageSize generano un errore. Il framework non impone un limite alle dimensioni di un SignalR messaggio dall'hub a un client.

Quando SignalR la registrazione non è impostata su Debug o Trace, nella console degli strumenti di sviluppo del browser viene visualizzato un errore relativo alle dimensioni del messaggio:

Errore: connessione disconnessa con errore 'Errore: il server ha restituito un errore alla chiusura: Connessione chiusa con un errore'.

Quando la registrazione lato server è impostata su SignalR o Trace, viene visualizzato un errore relativo alle dimensioni del messaggio.

appsettings.Development.json:

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

Errore:

System.IO.InvalidDataException: è stata superata la dimensione massima del messaggio di 32768B. Le dimensioni del messaggio possono essere configurate in AddHubOptions.

Un approccio prevede l'aumento del limite impostando MaximumReceiveMessageSize nel Program file. L'esempio seguente imposta la dimensione massima del messaggio di ricezione su 64 KB:

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

L'aumento del SignalR limite di dimensioni dei messaggi in arrivo comporta il costo di richiedere più risorse server e aumenta il rischio di attacchi Denial of Service (DoS). Inoltre, la lettura di una grande quantità di contenuto in memoria come stringhe o matrici di byte può anche comportare allocazioni che interagiscono male con il Garbage Collector, causando ulteriori penalizzazioni delle prestazioni.

Un'opzione migliore per la lettura di payload di grandi dimensioni consiste nell'inviare il contenuto in blocchi più piccoli ed elaborare il payload come .Stream Questa operazione può essere usata durante la lettura di payload JSON di interoperabilità JavaScript (JS) di grandi dimensioni o se JS i dati di interoperabilità sono disponibili come byte non elaborati. Per un esempio che illustra l'invio di payload binari di grandi dimensioni nelle app lato server che utilizzano tecniche simili al InputFile componente, vedere l'app di esempio Binary Submit e il campione del componente BlazorInputLargeTextArea.

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

I modelli che elaborano payload di grandi dimensioni possono anche usare direttamente l'interoperabilità di streaming. Per altre informazioni, vedere Chiamare metodi .NET da funzioni JavaScript in ASP.NET Core Blazor. Per un esempio di moduli che trasmettono i dati <textarea> al server, vedere Risolvere i problemi nei moduli di ASP.NET CoreBlazor.

Un approccio prevede l'aumento del limite impostando MaximumReceiveMessageSize nel Program file. L'esempio seguente imposta la dimensione massima del messaggio di ricezione su 64 KB:

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

L'aumento del SignalR limite di dimensioni dei messaggi in arrivo comporta il costo di richiedere più risorse server e aumenta il rischio di attacchi Denial of Service (DoS). Inoltre, la lettura di una grande quantità di contenuto in memoria come stringhe o matrici di byte può anche comportare allocazioni che interagiscono male con il Garbage Collector, causando ulteriori penalizzazioni delle prestazioni.

Un'opzione migliore per la lettura di payload di grandi dimensioni consiste nell'inviare il contenuto in blocchi più piccoli ed elaborare il payload come .Stream Questa operazione può essere usata durante la lettura di payload JSON di interoperabilità JavaScript (JS) di grandi dimensioni o se JS i dati di interoperabilità sono disponibili come byte non elaborati. Per un esempio che illustra l'invio di payload binari di grandi dimensioni in Blazor Server usando tecniche simili al componente InputFile, vedi l'applicazione di esempio Binary Submit e l'esempio del componente BlazorInputLargeTextArea.

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

I modelli che elaborano payload di grandi dimensioni possono anche usare direttamente l'interoperabilità di streaming. Per altre informazioni, vedere Chiamare metodi .NET da funzioni JavaScript in ASP.NET Core Blazor. Per un esempio di moduli che trasmette dati <textarea> in un'app Blazor Server, vedere Risoluzione dei problemi di ASP.NET Core Blazor moduli.

Aumentare il limite impostando MaximumReceiveMessageSize in Startup.ConfigureServices:

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

L'aumento del SignalR limite di dimensioni dei messaggi in arrivo comporta il costo di richiedere più risorse server e aumenta il rischio di attacchi Denial of Service (DoS). Inoltre, la lettura di una grande quantità di contenuto in memoria come stringhe o matrici di byte può anche comportare allocazioni che interagiscono male con il Garbage Collector, causando ulteriori penalizzazioni delle prestazioni.

Quando si sviluppa codice che trasferisce una grande quantità di dati, tenere presente quanto segue:

  • Sfruttare il supporto dell'interoperabilità di streaming JS nativo per trasferire dati superiori al limite di dimensioni dei SignalR messaggi in ingresso:
  • Suggerimenti generali:
    • Non allocare oggetti di grandi dimensioni nel codice C# JS.
    • Liberare memoria utilizzata al termine o all'annullamento del processo.
    • Applicare i requisiti aggiuntivi seguenti per scopi di sicurezza:
      • Dichiarare la dimensione massima del file o dei dati che è possibile passare.
      • Dichiarare la frequenza di caricamento minima dal client al server.
    • Dopo che i dati sono stati ricevuti dal server, i dati possono essere:
      • Archiviato temporaneamente in un buffer di memoria fino a quando non vengono raccolti tutti i segmenti.
      • Consumata immediatamente. Ad esempio, i dati possono essere archiviati immediatamente in un database o scritti su disco quando ogni segmento viene ricevuto.
  • Suddividere i dati in parti più piccole e inviare i segmenti di dati in sequenza fino a quando non vengono ricevuti tutti i dati dal server.
  • Non allocare oggetti di grandi dimensioni nel codice C# JS.
  • Non bloccare il thread principale dell'interfaccia utente per lunghi periodi durante l'invio o la ricezione di dati.
  • Liberare memoria utilizzata al termine o all'annullamento del processo.
  • Applicare i requisiti aggiuntivi seguenti per scopi di sicurezza:
    • Dichiarare la dimensione massima del file o dei dati che è possibile passare.
    • Dichiarare la frequenza di caricamento minima dal client al server.
  • Dopo che i dati sono stati ricevuti dal server, i dati possono essere:
    • Archiviato temporaneamente in un buffer di memoria fino a quando non vengono raccolti tutti i segmenti.
    • Consumata immediatamente. Ad esempio, i dati possono essere archiviati immediatamente in un database o scritti su disco quando ogni segmento viene ricevuto.

Blazor Configurazione della route dell'endpoint dell'hub sul lato server

Nel file Program, chiamare MapBlazorHub per eseguire il mapping di BlazorHub al percorso predefinito dell'app. Lo Blazor script (blazor.*.js) punta automaticamente all'endpoint creato da MapBlazorHub.

Riflettere lo stato della connessione lato server nell'interfaccia utente

Se il client rileva una connessione persa al server, viene visualizzata un'interfaccia utente predefinita all'utente mentre il client tenta di riconnettersi:

Interfaccia utente di riconnessione predefinita.

Interfaccia utente di riconnessione predefinita.

Se la riconnessione non riesce, all'utente viene richiesto di riprovare o ricaricare la pagina:

Interfaccia utente di ripetizione dei tentativi predefinita.

Interfaccia utente di ripetizione dei tentativi predefinita.

Se la riconnessione ha esito positivo, lo stato utente viene spesso perso. È possibile aggiungere codice personalizzato a qualsiasi componente per salvare e ricaricare lo stato utente in caso di errori di connessione. Per ulteriori informazioni, vedere gestione dello stato di ASP.NET CoreBlazor.

Per creare elementi dell'interfaccia utente che tengono traccia dello stato di riconnessione, la tabella seguente descrive:

  • Set di classi CSS components-reconnect-* (classe Css colonna) impostate o annullate da Blazor su un elemento con un id di components-reconnect-modal.
  • Evento components-reconnect-state-changed (colonnaEvent) che indica una modifica dello stato di riconnessione.
Classe CSS Evento Indica...
components-reconnect-show show Connessione persa. Il client sta tentando di riconnettersi. Viene visualizzato il modale di riconnessione.
components-reconnect-hide hide Viene ristabilita una connessione attiva al server. Il modello di riconnessione è chiuso.
components-reconnect-retrying retrying Il client sta tentando di riconnettersi.
components-reconnect-failed failed La riconnessione non è riuscita, probabilmente a causa di un errore di rete.
components-reconnect-rejected rejected Riconnessione rifiutata.

Quando lo stato di riconnessione in components-reconnect-state-changed è failed, chiamare Blazor.reconnect() in JavaScript per tentare la riconnessione.

Quando la modifica dello stato di riconnessione è rejected, il server è stato raggiunto ma ha rifiutato la connessione e lo stato dell'utente nel server viene perso. Per ricaricare l'app, chiamare location.reload() in JavaScript. Questo stato di connessione può risultare quando:

  • Si verifica un crash del circuito sul lato server.
  • Il client è disconnesso abbastanza a lungo per consentire al server di eliminare lo stato dell'utente. Le istanze dei componenti dell'utente vengono eliminate.
  • Il server viene riavviato o il processo di lavoro dell'app viene riciclato.

Lo sviluppatore aggiunge un listener di eventi nell'elemento modale di riconnessione per monitorare e reagire alle modifiche dello stato di riconnessione, come illustrato nell'esempio seguente:

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

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

Un elemento con id pari a components-reconnect-max-retries mostra il massimo numero di tentativi di riconnessione:

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

Un elemento con un id di components-reconnect-current-attempt visualizza il tentativo di riconnessione corrente:

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

Un elemento con id pari a components-seconds-to-next-attempt visualizza il numero di secondi al tentativo di riconnessione successivo.

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

Il modello di progetto Blazor Web App include un componente ReconnectModal (Layout/ReconnectModal.razor) con foglio di stile collocato e file JavaScript (ReconnectModal.razor.css, ReconnectModal.razor.js) che possono essere personalizzati in base alle esigenze. Questi file possono essere esaminati nell'origine di riferimento ASP.NET Core o esaminando un'app creata dal modello di progetto Blazor Web App. Il componente viene aggiunto al progetto quando il progetto viene creato in Visual Studio con modalità di rendering interattiva impostata su Server o Automatico o creato con l'interfaccia della riga di comando di .NET con l'opzione --interactivity server (impostazione predefinita) o --interactivity auto.

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Per personalizzare l'interfaccia utente, definire un singolo elemento contenente un id di tipo components-reconnect-modal nei contenuti dell'elemento <body>. Nell'esempio seguente, l'elemento viene inserito nel componente App.

App.razor:

Per personalizzare l'interfaccia utente, definire un singolo elemento contenente un id di tipo components-reconnect-modal nei contenuti dell'elemento <body>. Nell'esempio seguente l'elemento viene inserita nella pagina host.

Pages/_Host.cshtml:

Per personalizzare l'interfaccia utente, definire un singolo elemento contenente un id di tipo components-reconnect-modal nei contenuti dell'elemento <body>. Nell'esempio seguente l'elemento viene inserita nella pagina di layout.

Pages/_Layout.cshtml:

Per personalizzare l'interfaccia utente, definire un singolo elemento contenente un id di tipo components-reconnect-modal nei contenuti dell'elemento <body>. Nell'esempio seguente l'elemento viene inserita nella pagina host.

Pages/_Host.cshtml:

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

Nota

Se l'app esegue il rendering di più elementi con un id di components-reconnect-modal, solo il primo elemento renderizzato riceve le modifiche della classe CSS per visualizzare o nascondere l'elemento.

Aggiungere gli stili CSS seguenti al foglio di stile del sito.

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

Nella tabella seguente vengono descritte le classi CSS applicate all'elemento components-reconnect-modal dal Blazor framework.

Classe CSS Indica...
components-reconnect-show Connessione persa. Il client sta tentando di riconnettersi. Mostra la finestra modale.
components-reconnect-hide Viene ristabilita una connessione attiva al server. Nascondere il modale.
components-reconnect-failed La riconnessione non è riuscita, probabilmente a causa di un errore di rete. Per tentare la riconnessione, chiamare window.Blazor.reconnect() in JavaScript.
components-reconnect-rejected Riconnessione rifiutata. Il server è stato raggiunto ma ha rifiutato la connessione e lo stato dell'utente nel server viene perso. Per ricaricare l'app, chiamare location.reload() in JavaScript. Questo stato di connessione può risultare quando:
  • Si verifica un crash del circuito sul lato server.
  • Il client è disconnesso abbastanza a lungo per consentire al server di eliminare lo stato dell'utente. Le istanze dei componenti dell'utente vengono eliminate.
  • Il server viene riavviato o il processo di lavoro dell'app viene riciclato.

Personalizzare il ritardo prima che venga visualizzata l'interfaccia utente di riconnessione impostando la transition-delay proprietà nel css del sito per l'elemento modale. L'esempio seguente imposta il ritardo di transizione da 500 ms (impostazione predefinita) a 1.000 ms (1 secondo).

wwwroot/app.css:

wwwroot/css/site.css:

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

Per visualizzare il tentativo di riconnessione corrente, definire un elemento con un id di components-reconnect-current-attempt. Per visualizzare il numero massimo di tentativi di riconnessione, definire un elemento con un id impostato su components-reconnect-max-retries. L'esempio seguente inserisce questi elementi all'interno di un elemento modale tentativo di riconnessione dopo l'esempio precedente.

<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 viene visualizzata la modale di riconnessione personalizzata, esegue il rendering del contenuto seguente con un contatore di tentativi di riconnessione:

C'è stato un problema con la connessione! (Tentativo di riconnessione corrente: 1/ 8)

Rendering lato server

Per impostazione predefinita, i componenti vengono prerisorsi nel server prima che venga stabilita la connessione client al server. Per altre informazioni, vedere componenti Prerender ASP.NET CoreRazor.

Per impostazione predefinita, i componenti vengono prerisorsi nel server prima che venga stabilita la connessione client al server. Per altre informazioni, vedere Component Tag Helper in ASP.NET Core.

Monitorare l'attività del circuito sul lato server

Monitorare l'attività del circuito in ingresso usando il CreateInboundActivityHandler metodo su CircuitHandler. L'attività del circuito in ingresso è qualsiasi attività inviata dal browser al server, come eventi dell'interfaccia utente o chiamate di interoperabilità JavaScript e .NET.

Ad esempio, puoi usare un gestore di attività del circuito per rilevare se il client è inattivo e registrarne l'ID del 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;
    }
}

Registrare il servizio nel Program file. Nell'esempio seguente viene configurato il timeout di inattività predefinito da cinque minuti a cinque secondi per testare l'implementazione precedente IdleCircuitHandler :

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

I gestori di attività del circuito forniscono anche un approccio per l'accesso ai servizi con ambito Blazor da altri ambiti non Blazor di inserimento delle dipendenze (DI). Per altre informazioni ed esempi, vedere:

Blazor avvio

Configura l'avvio manuale del circuito di BlazorSignalR nel file App.razor di un oggetto Blazor Web App.

Configurare l'avvio manuale del BlazorSignalR circuito nel Pages/_Host.cshtml file (Blazor Server):

Configurare l'avvio manuale del BlazorSignalR circuito nel Pages/_Layout.cshtml file (Blazor Server):

Configurare l'avvio manuale del BlazorSignalR circuito nel Pages/_Host.cshtml file (Blazor Server):

  • Aggiungere un autostart="false" attributo al <script> tag per lo blazor.*.js script.
  • Inserire uno script che chiama Blazor.start() dopo il caricamento dello Blazor script e all'interno del tag di chiusura </body> .

Quando autostart è disabilitato, qualsiasi aspetto dell'app che non dipende dal circuito funziona normalmente. Ad esempio, il routing lato client è operativo. Tuttavia, qualsiasi aspetto che dipende dal circuito non è operativo fino a quando Blazor.start() non viene chiamato. Il comportamento dell'app è imprevedibile senza un circuito stabilito. Ad esempio, i metodi dei componenti non vengono eseguiti mentre il circuito è disconnesso.

Per ulteriori informazioni, tra cui come inizializzare Blazor quando il documento è pronto e come concatenare a un JS Promise, consulta ASP.NET Core Blazor startup.

Configurare SignalR Timeout e Keep-Alive nel client

Configurare i valori seguenti per il client:

  • withServerTimeout: configura il timeout del server in millisecondi. Se questo timeout è trascorso senza ricevere messaggi dal server, la connessione viene terminata con un errore. Il valore di timeout predefinito è di 30 secondi. Il timeout del server deve essere almeno doppio del valore assegnato all'intervallo Keep-Alive (withKeepAliveInterval).
  • withKeepAliveInterval: configura l'intervallo Keep-Alive in millisecondi (intervallo predefinito in cui eseguire il ping del server). Questa impostazione consente al server di rilevare disconnessioni forzate, ad esempio quando un client scollega il proprio computer dalla rete. Il ping viene eseguito al massimo con la frequenza con cui viene eseguito il ping del server. Se il server esegue il ping ogni cinque secondi, assegnare un valore inferiore a 5000 (5 secondi) farà comunque in modo che esegua il ping ogni cinque secondi. Il valore predefinito è 15 secondi. L'intervallo Keep-Alive deve essere minore o uguale alla metà del valore assegnato al timeout del server (withServerTimeout).

L'esempio seguente per il App.razor file (Blazor Web App) mostra l'assegnazione dei valori predefiniti.

Blazor Web App:

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

L'esempio seguente per il Pages/_Host.cshtml file (Blazor Server, tutte le versioni tranne ASP.NET Core in .NET 6) o Pages/_Layout.cshtml il file (Blazor Server, ASP.NET Core in .NET 6).

Blazor Server:

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

Nell'esempio precedente il {BLAZOR SCRIPT} segnaposto è il percorso dello script e il Blazor nome del file. Per la posizione dello script e il percorso da usare, vedere la struttura del progetto ASP.NET Core.

Quando si crea una connessione hub in un componente, impostare ServerTimeout (impostazione predefinita: 30 secondi) e KeepAliveInterval (impostazione predefinita: 15 secondi) in HubConnectionBuilder. Impostare il valore di HandshakeTimeout (predefinito: 15 secondi) sull'elemento costruito HubConnection. L'esempio seguente illustra l'assegnazione di valori predefiniti:

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

Configurare i valori seguenti per il client:

  • serverTimeoutInMilliseconds: Il tempo di attesa del server in millisecondi. Se questo timeout è trascorso senza ricevere messaggi dal server, la connessione viene terminata con un errore. Il valore di timeout predefinito è di 30 secondi. Il timeout del server deve essere almeno doppio del valore assegnato all'intervallo Keep-Alive (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: intervallo predefinito in cui effettuare il ping del server. Questa impostazione consente al server di rilevare disconnessioni forzate, ad esempio quando un client scollega il proprio computer dalla rete. Il ping viene eseguito al massimo con la frequenza con cui viene eseguito il ping del server. Se il server esegue il ping ogni cinque secondi, assegnare un valore inferiore a 5000 (5 secondi) farà comunque in modo che esegua il ping ogni cinque secondi. Il valore predefinito è 15 secondi. L'intervallo Keep-Alive deve essere minore o uguale alla metà del valore assegnato al timeout del server (serverTimeoutInMilliseconds).

L'esempio seguente per il Pages/_Host.cshtml file (Blazor Server, tutte le versioni tranne ASP.NET Core in .NET 6) o Pages/_Layout.cshtml il file (Blazor Server, ASP.NET Core in .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>

Nell'esempio precedente il {BLAZOR SCRIPT} segnaposto è il percorso dello script e il Blazor nome del file. Per la posizione dello script e il percorso da usare, vedere la struttura del progetto ASP.NET Core.

Quando si crea una connessione hub in un componente, impostare ServerTimeout (impostazione predefinita: 30 secondi), HandshakeTimeout (impostazione predefinita: 15 secondi) e KeepAliveInterval (impostazione predefinita: 15 secondi) nell'oggetto compilato HubConnection. L'esempio seguente illustra l'assegnazione di valori predefiniti:

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

Quando si modificano i valori del timeout del server (ServerTimeout) o l'intervallo Keep-Alive (KeepAliveInterval):

  • Il timeout del server deve essere pari almeno al doppio del valore assegnato all'intervallo Keep-Alive.
  • L'intervallo Keep-Alive deve essere minore o uguale alla metà del valore assegnato al timeout del server.

Per altre informazioni, vedere le sezioni Distribuzione globale e errori di connessione degli articoli seguenti:

Modificare il gestore di riconnessione lato server

Gli eventi di connessione del circuito del gestore di riconnessione possono essere modificati per comportamenti personalizzati, ad esempio:

  • Per notificare all'utente se la connessione viene eliminata.
  • Per eseguire la registrazione (dal client) quando un circuito è connesso.

Per modificare gli eventi di connessione, registrare i callback per le modifiche di connessione seguenti:

  • Le connessioni cadute usano onConnectionDown.
  • Le connessioni stabilite/ristabilite usano onConnectionUp.

Sia onConnectionDown che onConnectionUp devono essere specificati.

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>

Nell'esempio precedente il {BLAZOR SCRIPT} segnaposto è il percorso dello script e il Blazor nome del file. Per la posizione dello script e il percorso da usare, vedere la struttura del progetto ASP.NET Core.

Aggiornare automaticamente la pagina quando la riconnessione lato server non riesce

Il comportamento di riconnessione predefinito richiede all'utente di eseguire un'azione manuale per aggiornare la pagina dopo che la riconnessione non riesce. Tuttavia, è possibile usare un gestore di riconnessione personalizzato per aggiornare automaticamente la pagina:

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>

Nell'esempio precedente il {BLAZOR SCRIPT} segnaposto è il percorso dello script e il Blazor nome del file. Per la posizione dello script e il percorso da usare, vedere la struttura del progetto ASP.NET Core.

Creare il file seguente 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;
      }
    }
  });
})();

Per altre informazioni sull'avvio di Blazor, vedere Avvio di ASP.NET Core Blazor.

Modificare il numero di tentativi e l'intervallo di riconnessione sul lato server

Per modificare il numero di tentativi di riconnessione e l'intervallo, impostare il numero di tentativi (maxRetries) e il periodo in millisecondi consentiti per ogni tentativo di ripetizione (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>

Nell'esempio precedente il {BLAZOR SCRIPT} segnaposto è il percorso dello script e il Blazor nome del file. Per la posizione dello script e il percorso da usare, vedere la struttura del progetto ASP.NET Core.

Quando l'utente torna a un'app con un circuito disconnesso, la riconnessione viene tentata immediatamente anziché attendere la durata dell'intervallo di riconnessione successivo. Questo comportamento cerca di riprendere la connessione il più rapidamente possibile per l'utente.

L'intervallo di riconnessione predefinito usa una strategia di backoff calcolata. I primi tentativi di riconnessione si verificano in rapida successione prima che vengano introdotti ritardi calcolati tra i tentativi. La logica predefinita per il calcolo dell'intervallo di ripetizione dei tentativi è un dettaglio di implementazione soggetto a modifiche senza preavviso, ma è possibile trovare la logica predefinita usata dal Blazor framework nella computeDefaultRetryInterval funzione (origine di riferimento).

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Personalizzare il comportamento dell'intervallo di ripetizione dei tentativi specificando una funzione per calcolare l'intervallo di ripetizione dei tentativi. Nell'esempio di backoff esponenziale seguente il numero di tentativi di riconnessione precedenti viene moltiplicato per 1.000 ms per calcolare l'intervallo di ripetizione dei tentativi. Quando il numero di tentativi precedenti di riconnessione (previousAttempts) è maggiore del limite massimo di tentativi (maxRetries), null viene assegnato all'intervallo tra tentativi (retryIntervalMilliseconds) per interrompere ulteriori tentativi di riconnessione:

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

Un'alternativa consiste nel specificare la sequenza esatta di intervalli di ripetizione dei tentativi. Dopo l'ultimo intervallo di ripetizione dei tentativi specificato, i nuovi tentativi vengono interrotti perché la retryIntervalMilliseconds funzione restituisce undefined:

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

Per altre informazioni sull'avvio di Blazor, vedere Avvio di ASP.NET Core Blazor.

Controllare quando viene visualizzata l'interfaccia utente di riconnessione

Il controllo quando viene visualizzata l'interfaccia utente di riconnessione può essere utile nelle situazioni seguenti:

  • Un'applicazione distribuita visualizza spesso l'interfaccia utente di riconnessione a causa di timeout di ping causati dalla latenza della rete interna o di Internet e si desidera aumentare il ritardo.
  • Un'app dovrebbe segnalare tempestivamente agli utenti che la connessione è stata interrotta, e si desidera ridurre il ritardo.

La tempistica con cui appare l'interfaccia utente di riconnessione è influenzata dalla modifica dell'intervallo Keep-Alive e dei timeout sul client. L'interfaccia utente di riconnessione viene visualizzata quando viene raggiunto il timeout del server nel client (withServerTimeoutsezione Configurazione client). Tuttavia, la modifica del valore di withServerTimeout richiede modifiche ad altre impostazioni Keep-Alive, timeout e handshake descritte nelle indicazioni seguenti.

Come raccomandazioni generali per le indicazioni seguenti:

  • L'intervallo Keep-Alive deve corrispondere tra le configurazioni client e server.
  • I timeout devono essere almeno il doppio del valore assegnato all'intervallo Keep-Alive.

Configurazione del server

Impostare i seguenti elementi:

  • ClientTimeoutInterval (impostazione predefinita: 30 secondi): i client dell'intervallo di tempo devono inviare un messaggio prima che il server chiuda la connessione.
  • HandshakeTimeout (impostazione predefinita: 15 secondi): intervallo usato dal server per timeout delle richieste di handshake in ingresso da parte dei client.
  • KeepAliveInterval (impostazione predefinita: 15 secondi): intervallo usato dal server per inviare ping keep-alive ai client connessi. Si noti che è disponibile anche un'impostazione di intervallo Keep-Alive nel client, che deve corrispondere al valore del server.

Il ClientTimeoutInterval e il HandshakeTimeout possono essere aumentati, e il KeepAliveInterval può rimanere lo stesso. La considerazione importante è che, se si modificano i valori, assicurarsi che i timeout siano almeno il doppio del valore dell'intervallo Keep-Alive e che l'intervallo Keep-Alive corrisponda tra server e client. Per altre informazioni, vedere la sezione Configurare SignalR i time-out e Keep-Alive nel client.

Nell'esempio seguente :

  • Il ClientTimeoutInterval è aumentato a 60 secondi (valore predefinito: 30 secondi).
  • Il HandshakeTimeout viene aumentato a 30 secondi (valore predefinito: 15 secondi).
  • Il KeepAliveInterval non è impostato nel codice dello sviluppatore e utilizza il valore predefinito di 15 secondi. La riduzione del valore dell'intervallo Keep-Alive aumenta la frequenza dei ping di comunicazione, aumentando così il carico sull'app, sul server e sulla rete. È necessario prestare attenzione per evitare di introdurre prestazioni scarse quando si riduce l'intervallo Keep-Alive.

Blazor Web App (.NET 8 o versioni successive) nel file Program del progetto server:

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

Blazor Server nel Program file:

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

Per altre informazioni, vedere la sezione Opzioni del gestore del circuito lato server.

Configurazione del client

Impostare i seguenti elementi:

  • withServerTimeout (impostazione predefinita: 30 secondi): configura il timeout del server, specificato in millisecondi, per la connessione dell'hub del circuito.
  • withKeepAliveInterval (impostazione predefinita: 15 secondi): intervallo, specificato in millisecondi, in cui la connessione invia messaggi Keep-Alive.

Il timeout del server può essere aumentato e l'intervallo Keep-Alive può rimanere invariato. La considerazione importante è che se si modificano i valori, assicurarsi che il timeout del server sia almeno doppio del valore dell'intervallo Keep-Alive e che i valori dell'intervallo Keep-Alive corrispondano tra server e client. Per altre informazioni, vedere la sezione Configurare SignalR i time-out e Keep-Alive nel client.

Nell'esempio di configurazione di avvio seguente (posizione dello Blazor script), viene usato un valore personalizzato di 60 secondi per il timeout del server. L'intervallo Keep-Alive (withKeepAliveInterval) non è impostato e usa il valore predefinito di 15 secondi.

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>

Quando si crea una connessione hub in un componente, impostare il timeout del server (WithServerTimeout, impostazione predefinita: 30 secondi) su HubConnectionBuilder. Impostare il valore di HandshakeTimeout (predefinito: 15 secondi) sull'elemento costruito HubConnection. Verificare che i timeout siano almeno il doppio dell'intervallo Keep-Alive (WithKeepAliveInterval/KeepAliveInterval) e che il valore Keep-Alive corrisponda tra server e client.

L'esempio seguente è basato sul componente Index nel tutorial con SignalRBlazor. Il timeout del server viene aumentato a 60 secondi e il timeout dell'handshake viene aumentato a 30 secondi. L'intervallo Keep-Alive non è impostato e usa il valore predefinito di 15 secondi.

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

Impostare i seguenti elementi:

  • serverTimeoutInMilliseconds (impostazione predefinita: 30 secondi): configura il timeout del server, specificato in millisecondi, per la connessione dell'hub del circuito.
  • keepAliveIntervalInMilliseconds (impostazione predefinita: 15 secondi): intervallo, specificato in millisecondi, in cui la connessione invia messaggi Keep-Alive.

Il timeout del server può essere aumentato e l'intervallo Keep-Alive può rimanere invariato. La considerazione importante è che se si modificano i valori, assicurarsi che il timeout del server sia almeno doppio del valore dell'intervallo Keep-Alive e che i valori dell'intervallo Keep-Alive corrispondano tra server e client. Per altre informazioni, vedere la sezione Configurare SignalR i time-out e Keep-Alive nel client.

Nell'esempio di configurazione di avvio seguente (posizione dello Blazor script), viene usato un valore personalizzato di 60 secondi per il timeout del server. L'intervallo Keep-Alive (keepAliveIntervalInMilliseconds) non è impostato e usa il valore predefinito di 15 secondi.

In 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>

Quando si crea una connessione hub in un componente, impostare il ServerTimeout (impostazione predefinita: 30 secondi) e il HandshakeTimeout (impostazione predefinita: 15 secondi) sull'oggetto creato HubConnection. Verificare che i timeout siano almeno due volte l'intervallo Keep-Alive. Verificare che l'intervallo Keep-Alive corrisponda tra server e client.

L'esempio seguente è basato sul componente Index nel tutorial con SignalRBlazor. ServerTimeout viene aumentato a 60 secondi e HandshakeTimeout viene aumentato a 30 secondi. L'intervallo Keep-Alive (KeepAliveInterval) non è impostato e usa il valore predefinito di 15 secondi.

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

Disconnettere il circuito di BlazorSignalR dal client

Blazor Il circuito di SignalR è disconnesso quando viene attivato l'evento unload della pagina. Per disconnettere il circuito per altri scenari nel client, richiamare Blazor.disconnect nel gestore eventi appropriato. Nell'esempio seguente il circuito viene disconnesso quando la pagina è nascosta (pagehide evento):

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

Per altre informazioni sull'avvio di Blazor, vedere Avvio di ASP.NET Core Blazor.

Gestore del circuito lato server

È possibile definire un gestore del circuito, che consente l'esecuzione del codice in caso di modifiche allo stato del circuito di un utente. Un gestore del circuito viene implementato derivando da CircuitHandler e registrando la classe nel contenitore dei servizi dell'app. L'esempio seguente di un gestore di circuito tiene traccia delle connessioni aperte SignalR.

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

I gestori di circuito vengono registrati utilizzando l'Inversione delle Dipendenze (DI). Le istanze con ambito vengono create per ogni istanza di un circuito. Usando nell'esempio TrackingCircuitHandler precedente, viene creato un servizio singleton perché è necessario tenere traccia dello stato di tutti i circuiti.

Nel file Program:

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

In Startup.ConfigureServices di Startup.cs:

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Se i metodi di un gestore del circuito personalizzato generano un'eccezione non gestita, l'eccezione è irreversibile per il circuito. Per gestire le eccezioni nel codice di un gestore o nei metodi invocati, racchiudere il codice in una o più istruzioni try-catch con la gestione e la registrazione degli errori.

Quando un circuito termina perché un utente si è disconnesso e il framework sta ripulendo lo stato del circuito, il framework elimina l'ambito di DI del circuito. L'eliminazione dell'ambito elimina tutti i servizi DI con ambito di circuito che implementano System.IDisposable. Se un servizio DI genera un'eccezione non gestita durante l'eliminazione, il framework registra l'eccezione. Per altre informazioni, vedere ASP.NET Core Blazor dependency injection.

Gestore del circuito lato server per acquisire gli utenti per i servizi personalizzati

Usare CircuitHandler per catturare un utente da AuthenticationStateProvider e assegnare tale utente a un servizio. Per ulteriori informazioni e codice di esempio, vedere ASP.NET Core lato server e Blazor Web App scenari di sicurezza aggiuntivi.

Chiusura di circuiti quando non sono presenti componenti Interactive Server rimanenti

I componenti di Interactive Server gestiscono gli eventi dell'interfaccia utente Web usando una connessione in tempo reale con il browser denominato circuito. Un circuito e il suo stato associato vengono creati quando un componente principale del Server Interattivo è renderizzato. Il circuito viene chiuso quando nella pagina non sono presenti componenti Interactive Server rimanenti, che liberano risorse server.

Avviare il circuito SignalR in un URL diverso

Impedire l'avvio automatico dell'app aggiungendo autostart="false" al tag Blazor<script> (posizione dello script di avvio Blazor). Impostare manualmente l'URL del circuito usando Blazor.start. Negli esempi seguenti viene usato il percorso /signalr.

Blazor Web App:

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

Blazor Server:

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

Aggiungere la seguente chiamata MapBlazorHub con il percorso dell'hub alla pipeline di elaborazione del middleware nel file Program dell'applicazione server.

Blazor Web App:

app.MapBlazorHub("/signalr");

Blazor Server:

Lasciare la chiamata esistente a MapBlazorHub nel file e aggiungere una nuova chiamata a MapBlazorHub con il percorso:

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

Rappresentazione per l'autenticazione di Windows

Le connessioni hub autenticate (HubConnection) vengono create con UseDefaultCredentials per indicare l'uso delle credenziali predefinite per le richieste HTTP. Per altre informazioni, vedere Autenticazione e autorizzazione in ASP.NET Core SignalR.

Quando l'app è in esecuzione in IIS Express come utente connesso in Autenticazione di Windows, probabilmente l'account personale o aziendale dell'utente, le credenziali predefinite sono quelle dell'utente connesso.

Quando l'app viene pubblicata in IIS, l'app viene eseguita nel pool di applicazioni Identity. Il HubConnection si connette come account "utente" IIS che ospita l'app, non l'utente che accede alla pagina.

Implementare impersonificazione con il HubConnection per usare l'identità dell'utente che naviga.

Nell'esempio seguente :

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

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

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

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

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

Nel codice precedente, NavManager è un NavigationManagere AuthenticationStateProvider è un'istanza di servizio AuthenticationStateProvider (AuthenticationStateProvider documentazione).

Risorse aggiuntive sul lato server