Condividi tramite


ASP.NET gestione dello stato core Blazor

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 descrive gli approcci comuni per la gestione dei dati (stato) di un utente mentre usano un'app e tra le sessioni del browser.

Nota

Gli esempi di codice in questo articolo adottano tipi di riferimento nullable (NRT) e l'analisi statica dello stato null del compilatore .NET, supportati in ASP.NET Core in .NET 6 o versione successiva. Quando la destinazione è ASP.NET Core 5.0 o versione precedente, rimuovere la designazione di tipo Null (?) dai tipi negli esempi dell'articolo.

Mantenere lo stato utente

Il lato Blazor server è un framework di app con stato. Nella maggior parte dei casi, l'app mantiene una connessione al server. Lo stato dell'utente viene mantenuto nella memoria del server in un circuito.

Esempi di stato utente contenuti in un circuito includono:

  • La gerarchia delle istanze del componente e il relativo output di rendering più recente nell'interfaccia utente sottoposta a rendering.
  • Valori dei campi e delle proprietà nelle istanze del componente.
  • Dati contenuti nelle istanze del servizio di inserimento delle dipendenze con ambito del circuito.

Lo stato utente può essere trovato anche nelle variabili JavaScript nel set di memoria del browser tramite chiamate di interoperabilità JavaScript.

Se un utente riscontra una perdita temporanea di connessione di rete, Blazor tenta di riconnettere l'utente al circuito originale con lo stato originale. Tuttavia, la riconnessione di un utente al circuito originale nella memoria del server non è sempre possibile:

  • Il server non può mantenere un circuito disconnesso per sempre. Il server deve rilasciare un circuito disconnesso dopo un timeout o quando il server è sotto pressione di memoria.
  • Negli ambienti di distribuzione multiserver con bilanciamento del carico, i singoli server possono avere esito negativo o essere rimossi automaticamente quando non è più necessario gestire il volume complessivo delle richieste. Le richieste di elaborazione del server originali per un utente potrebbero non essere disponibili quando l'utente tenta di riconnettersi.
  • L'utente potrebbe chiudere e riaprire il browser o ricaricare la pagina, rimuovendo qualsiasi stato mantenuto nella memoria del browser. Ad esempio, i valori delle variabili JavaScript impostati tramite chiamate di interoperabilità JavaScript vengono persi.

Quando un utente non può essere riconnesso al circuito originale, l'utente riceve un nuovo circuito con uno stato vuoto. Equivale alla chiusura e alla riapertura di un'app desktop.

Rendere persistente lo stato tra circuiti

In genere, mantenere lo stato tra circuiti in cui gli utenti creano attivamente dati, non semplicemente leggendo i dati già esistenti.

Per mantenere lo stato tra circuiti, l'app deve rendere persistenti i dati in un percorso di archiviazione diverso rispetto alla memoria del server. La persistenza dello stato non è automatica. Per implementare la persistenza dei dati con stato, è necessario eseguire le operazioni necessarie per lo sviluppo dell'app.

La persistenza dei dati è in genere necessaria solo per lo stato di valore elevato che gli utenti hanno richiesto di creare. Negli esempi seguenti, il salvataggio permanente dello stato consente di risparmiare tempo o aiuti nelle attività commerciali:

  • Web form in più passaggi: richiede molto tempo per consentire a un utente di immettere nuovamente i dati per diversi passaggi completati di un modulo Web in più passaggi se il relativo stato viene perso. Un utente perde lo stato in questo scenario se si allontana dal modulo e restituisce in un secondo momento.
  • Carrello acquisti: qualsiasi componente commerciale importante di un'app che rappresenta potenziali ricavi può essere mantenuto. Un utente che perde il proprio stato, e quindi il carrello acquisti, può acquistare meno prodotti o servizi quando tornano al sito in un secondo momento.

Un'app può mantenere solo lo stato dell'app. Le interfacce utente non possono essere rese persistenti, ad esempio le istanze del componente e i relativi alberi di rendering. I componenti e gli alberi di rendering non sono in genere serializzabili. Per rendere persistente lo stato dell'interfaccia utente, ad esempio i nodi espansi di un controllo visualizzazione albero, l'app deve usare codice personalizzato per modellare il comportamento dello stato dell'interfaccia utente come stato dell'app serializzabile.

Dove rendere persistente lo stato

Esistono posizioni comuni per lo stato persistente:

Archiviazione lato server

Per la persistenza permanente dei dati che si estende su più utenti e dispositivi, l'app può usare l'archiviazione lato server. Le opzioni includono:

  • Archiviazione BLOB
  • Archiviazione chiave-valore
  • Database relazionale
  • Archiviazione tabelle

Dopo il salvataggio dei dati, lo stato dell'utente viene conservato e disponibile in qualsiasi nuovo circuito.

Per altre informazioni sulle opzioni di archiviazione dei dati di Azure, vedere quanto segue:

URL

Per i dati temporanei che rappresentano lo stato di navigazione, modellare i dati come parte dell'URL. Esempi di stato utente modellati nell'URL includono:

  • ID di un'entità visualizzata.
  • Numero di pagina corrente in una griglia di paging.

Il contenuto della barra degli indirizzi del browser viene conservato:

  • Se l'utente ricarica manualmente la pagina.
  • Se il server Web non è più disponibile e l'utente deve ricaricare la pagina per connettersi a un server diverso.

Per informazioni sulla definizione dei modelli di URL con la @page direttiva , vedere ASP.NET routing e navigazione coreBlazor.

Archiviazione del browser

Per i dati temporanei che l'utente sta creando attivamente, un percorso di archiviazione comunemente usato è il browser localStorage e sessionStorage le raccolte:

  • localStorage ha come ambito la finestra del browser. Se l'utente ricarica la pagina o si chiude e riapre il browser, lo stato persiste. Se l'utente apre più schede del browser, lo stato viene condiviso tra le schede. I dati vengono mantenuti fino localStorage a quando non vengono cancellati in modo esplicito.
  • sessionStorage è limitato alla scheda del browser. Se l'utente ricarica la scheda, lo stato persiste. Se l'utente chiude la scheda o il browser, lo stato viene perso. Se l'utente apre più schede del browser, ogni scheda ha una propria versione indipendente dei dati.

In genere, sessionStorage è più sicuro da usare. sessionStorage evita il rischio che un utente apra più schede e riscontri quanto segue:

  • Bug nell'archiviazione dello stato tra le schede.
  • Comportamento confuso quando una scheda sovrascrive lo stato di altre schede.

localStorage è la scelta migliore se l'app deve mantenere lo stato di chiusura e riaprire il browser.

Avvertenze per l'uso dell'archiviazione del browser:

  • Analogamente all'uso di un database lato server, il caricamento e il salvataggio dei dati sono asincroni.
  • La pagina richiesta non esiste nel browser durante la pre-gestione, quindi l'archiviazione locale non è disponibile durante la pre-distribuzione.
  • L'archiviazione di alcuni kilobyte di dati è ragionevole per rendere persistenti le app lato Blazor server. Oltre alcuni kilobyte, è necessario considerare le implicazioni sulle prestazioni perché i dati vengono caricati e salvati in rete.
  • Gli utenti possono visualizzare o manomettere i dati. ASP.NET Core Data Protection può ridurre il rischio. Ad esempio, ASP.NET Core Protected Browser Storage usa ASP.NET Protezione dati di base.

I pacchetti NuGet di terze parti forniscono API per l'uso di localStorage e sessionStorage. È opportuno prendere in considerazione la scelta di un pacchetto che usa in modo trasparente ASP.NET Protezione dati di base. La protezione dei dati crittografa i dati archiviati e riduce il rischio potenziale di manomissione dei dati archiviati. Se i dati serializzati json vengono archiviati in testo normale, gli utenti possono visualizzare i dati usando gli strumenti di sviluppo del browser e modificare anche i dati archiviati. La protezione dei dati semplici non è un problema. Ad esempio, la lettura o la modifica del colore archiviato di un elemento dell'interfaccia utente non rappresenta un rischio significativo per la sicurezza per l'utente o l'organizzazione. Evitare di consentire agli utenti di controllare o manomettere i dati sensibili.

ASP.NET Core Protected Browser Storage

ASP.NET Core Protected Browser Storage sfrutta ASP.NET protezione dei dati di base per localStorage e sessionStorage.

Nota

L'archiviazione browser protetta si basa su ASP.NET Core Data Protection ed è supportata solo per le app lato Blazor server.

Avviso

Microsoft.AspNetCore.ProtectedBrowserStorage è un pacchetto sperimentale non supportato che non è destinato all'uso in produzione.

Il pacchetto è disponibile solo per l'uso nelle app ASP.NET Core 3.1.

Impostazione

  1. Aggiungere un riferimento al pacchetto a Microsoft.AspNetCore.ProtectedBrowserStorage.

    Nota

    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). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

  2. _Host.cshtml Nel file aggiungere lo script seguente all'interno del tag di chiusura</body>:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. In Startup.ConfigureServiceschiamare AddProtectedBrowserStorage per aggiungere localStorage e sessionStorage servizi alla raccolta di servizi:

    services.AddProtectedBrowserStorage();
    

Salvare e caricare i dati all'interno di un componente

In qualsiasi componente che richiede il caricamento o il salvataggio dei dati nell'archiviazione del browser, usare la @inject direttiva per inserire un'istanza di uno dei seguenti elementi:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

La scelta dipende dal percorso di archiviazione del browser che si vuole usare. Nell'esempio sessionStorage seguente viene usato:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

La @using direttiva può essere inserita nel file dell'app _Imports.razor anziché nel componente. L'uso del _Imports.razor file rende lo spazio dei nomi disponibile per segmenti più grandi dell'app o dell'intera app.

Per rendere persistente il valore nel Counter componente di un'app in base alBlazor modello di progetto, modificare il IncrementCount metodo per usare ProtectedSessionStore.SetAsync:currentCount

private async Task IncrementCount()
{
    currentCount++;
    await ProtectedSessionStore.SetAsync("count", currentCount);
}

In app più grandi e più realistiche, l'archiviazione dei singoli campi è uno scenario improbabile. È più probabile che le app archiviino interi oggetti modello che includono uno stato complesso. ProtectedSessionStore serializza e deserializza automaticamente i dati JSON per archiviare oggetti di stato complessi.

Nell'esempio di codice precedente i currentCount dati vengono archiviati come sessionStorage['count'] nel browser dell'utente. I dati non vengono archiviati in testo normale, ma sono protetti usando ASP.NET Protezione dati di base. I dati crittografati possono essere controllati se sessionStorage['count'] vengono valutati nella console di sviluppo del browser.

Per recuperare i currentCount dati se l'utente torna al Counter componente in un secondo momento, incluso se l'utente si trova in un nuovo circuito, usare ProtectedSessionStore.GetAsync:

protected override async Task OnInitializedAsync()
{
    var result = await ProtectedSessionStore.GetAsync<int>("count");
    currentCount = result.Success ? result.Value : 0;
}
protected override async Task OnInitializedAsync()
{
    currentCount = await ProtectedSessionStore.GetAsync<int>("count");
}

Se i parametri del componente includono lo stato di navigazione, chiamare ProtectedSessionStore.GetAsync e assegnare unnull risultato diverso da OnParametersSetAsync, non OnInitializedAsync. OnInitializedAsync viene chiamato una sola volta quando viene creata la prima istanza del componente. OnInitializedAsync non viene chiamato di nuovo in un secondo momento se l'utente passa a un URL diverso mentre rimane nella stessa pagina. Per altre informazioni, vedere Ciclo di vita dei componenti di ASP.NET Core Razor.

Avviso

Gli esempi in questa sezione funzionano solo se il server non dispone di prerendering abilitato. Con la prerendering abilitata, viene generato un errore che spiega che non è possibile eseguire chiamate di interoperabilità JavaScript perché il componente viene pre-risolto.

Disabilitare la prerendering o aggiungere codice aggiuntivo per lavorare con la prerendering. Per altre informazioni sulla scrittura di codice che funziona con il prerendering, vedere la sezione Gestire la prerendering .

Gestire lo stato di caricamento

Poiché l'archiviazione del browser è accessibile in modo asincrono tramite una connessione di rete, è sempre presente un periodo di tempo prima che i dati vengano caricati e disponibili per un componente. Per ottenere risultati ottimali, eseguire il rendering di un messaggio durante il caricamento è in corso anziché visualizzare dati vuoti o predefiniti.

Un approccio consiste nel tenere traccia se i dati sono null, il che significa che i dati sono ancora in fase di caricamento. Nel componente predefinito Counter il conteggio viene mantenuto in un oggetto int. Rendere currentCount nullable aggiungendo un punto interrogativo (?) al tipo (int):

private int? currentCount;

Anziché visualizzare in modo incondizionato il conteggio e Increment il pulsante, visualizzare questi elementi solo se i dati vengono caricati controllando HasValue:

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

Gestire la prerendering

Durante la prerendering:

  • Non esiste una connessione interattiva al browser dell'utente.
  • Il browser non ha ancora una pagina in cui può eseguire codice JavaScript.

localStorage o sessionStorage non sono disponibili durante il pre-ordinamento. Se il componente tenta di interagire con l'archiviazione, viene generato un errore che spiega che non è possibile eseguire chiamate di interoperabilità JavaScript perché il componente viene pre-risolto.

Un modo per risolvere l'errore consiste nel disabilitare il prerendering. Questa è in genere la scelta migliore se l'app usa pesantemente l'archiviazione basata su browser. La pre-gestione aggiunge complessità e non trae vantaggio dall'app perché l'app non può prerendere alcun contenuto utile fino a localStorage quando o sessionStorage non sono disponibili.

Per disabilitare la prerendering, indicare la modalità di rendering con il prerender parametro impostato su false al componente di livello più alto nella gerarchia dei componenti dell'app che non è un componente radice.

Nota

Rendere interattivo un componente radice, ad esempio il App componente, non è supportato. Di conseguenza, il prerendering non può essere disabilitato direttamente dal App componente.

Per le app basate sul modello di Blazor Web App progetto, il prerendering viene in genere disabilitato in cui il Routes componente viene usato nel App componente (Components/App.razor):

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Disabilitare anche la prerendering per il HeadOutlet componente:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Per altre informazioni, vedere ASP.NET Modalità di rendering coreBlazor.

Per disabilitare la prerendering, aprire il _Host.cshtml file e modificare l'attributo dell'helper render-mode tag del componente in :Server

<component type="typeof(App)" render-mode="Server" />

Quando il prerendering è disabilitato, il prerendering del <head> contenuto è disabilitato.

La pre-distribuzione potrebbe essere utile per altre pagine che non usano localStorage o sessionStorage. Per mantenere la prerendering, rinviare l'operazione di caricamento fino a quando il browser non è connesso al circuito. Di seguito è riportato un esempio per l'archiviazione di un valore del contatore:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount;
    private bool isConnected;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedLocalStore.GetAsync<int>("count");
        currentCount = result.Success ? result.Value : 0;
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount = 0;
    private bool isConnected = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        currentCount = await ProtectedLocalStore.GetAsync<int>("count");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}

Considerare la conservazione dello stato in una posizione comune

Se molti componenti si basano sull'archiviazione basata su browser, l'implementazione del codice del provider di stato molte volte crea la duplicazione del codice. Un'opzione per evitare la duplicazione del codice consiste nel creare un componente padre del provider di stato che incapsula la logica del provider di stato. I componenti figlio possono funzionare con dati persistenti senza considerare il meccanismo di persistenza dello stato.

Nell'esempio seguente di un CounterStateProvider componente i dati dei contatori vengono resi persistenti in sessionStorage:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}

Nota

Per altre informazioni su RenderFragment, vedere ASP.NET Componenti di baseRazor.

Il CounterStateProvider componente gestisce la fase di caricamento non eseguendo il rendering del contenuto figlio fino al completamento del caricamento dello stato.

Per rendere lo stato accessibile a tutti i componenti di un'app, eseguire il wrapping del CounterStateProvider componente intorno a Router (<Router>...</Router>) nel Routes componente con rendering interattivo sul lato server globale (SSR interattivo).

Nel componente App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

Nel componente Routes (Components/Routes.razor):

Per usare il componente, eseguire il CounterStateProvider wrapping di un'istanza del componente intorno a qualsiasi altro componente che richiede l'accesso allo stato del contatore. Per rendere lo stato accessibile a tutti i componenti di un'app, eseguire il wrapping del CounterStateProvider App componente Router nel componente (App.razor):

<CounterStateProvider>
    <Router ...>
        ...
    </Router>
</CounterStateProvider>

Nota

Con la versione di ASP.NET Core 5.0.1 e per eventuali versioni 5.x aggiuntive, il componente Router include il parametro PreferExactMatches impostato su @true. Per altre informazioni, vedere Eseguire la migrazione da ASP.NET Core 3.1 a 5.0.

I componenti sottoposti a wrapping ricevono e possono modificare lo stato del contatore persistente. Il componente seguente Counter implementa il modello :

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>

@code {
    [CascadingParameter]
    private CounterStateProvider? CounterStateProvider { get; set; }

    private async Task IncrementCount()
    {
        if (CounterStateProvider is not null)
        {
            CounterStateProvider.CurrentCount++;
            await CounterStateProvider.SaveChangesAsync();
        }
    }
}

Il componente precedente non è necessario per interagire con ProtectedBrowserStorage, né gestisce una fase di "caricamento".

Per gestire la prerendering come descritto in precedenza, può essere modificato in CounterStateProvider modo che tutti i componenti che utilizzano i dati del contatore funzionino automaticamente con la pre-gestione. Per altre informazioni, vedere la sezione Gestire la prerendering .

In generale, è consigliabile usare il modello di componente padre del provider di stato:

  • Per utilizzare lo stato in molti componenti.
  • Se è presente un solo oggetto di stato di primo livello da rendere persistente.

Per rendere persistenti molti oggetti di stato diversi e utilizzare subset diversi di oggetti in posizioni diverse, è preferibile evitare di rendere persistente lo stato a livello globale.

Lo stato utente creato in un'app Blazor WebAssembly viene mantenuto nella memoria del browser.

Esempi di stato utente contenuti nella memoria del browser includono:

  • La gerarchia delle istanze del componente e il relativo output di rendering più recente nell'interfaccia utente sottoposta a rendering.
  • Valori dei campi e delle proprietà nelle istanze del componente.
  • Dati contenuti nelle istanze del servizio di inserimento delle dipendenze.Data held in dependency injection (DI) service instances.
  • Valori impostati tramite chiamate di interoperabilità JavaScript.

Quando un utente chiude e riapre il browser o ricarica la pagina, lo stato utente mantenuto nella memoria del browser viene perso.

Nota

L'archiviazione del browser protetto (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage spazio dei nomi) si basa su ASP.NET Core Data Protection ed è supportata solo per le app lato Blazor server.

Rendere persistente lo stato tra le sessioni del browser

In genere, mantenere lo stato tra le sessioni del browser in cui gli utenti creano attivamente dati, non semplicemente leggendo i dati già esistenti.

Per mantenere lo stato tra le sessioni del browser, l'app deve rendere persistenti i dati in un percorso di archiviazione diverso rispetto alla memoria del browser. La persistenza dello stato non è automatica. Per implementare la persistenza dei dati con stato, è necessario eseguire le operazioni necessarie per lo sviluppo dell'app.

La persistenza dei dati è in genere necessaria solo per lo stato di valore elevato che gli utenti hanno richiesto di creare. Negli esempi seguenti, il salvataggio permanente dello stato consente di risparmiare tempo o aiuti nelle attività commerciali:

  • Web form in più passaggi: richiede molto tempo per consentire a un utente di immettere nuovamente i dati per diversi passaggi completati di un modulo Web in più passaggi se il relativo stato viene perso. Un utente perde lo stato in questo scenario se si allontana dal modulo e restituisce in un secondo momento.
  • Carrello acquisti: qualsiasi componente commerciale importante di un'app che rappresenta potenziali ricavi può essere mantenuto. Un utente che perde il proprio stato, e quindi il carrello acquisti, può acquistare meno prodotti o servizi quando tornano al sito in un secondo momento.

Un'app può mantenere solo lo stato dell'app. Le interfacce utente non possono essere rese persistenti, ad esempio le istanze del componente e i relativi alberi di rendering. I componenti e gli alberi di rendering non sono in genere serializzabili. Per rendere persistente lo stato dell'interfaccia utente, ad esempio i nodi espansi di un controllo visualizzazione albero, l'app deve usare codice personalizzato per modellare il comportamento dello stato dell'interfaccia utente come stato dell'app serializzabile.

Dove rendere persistente lo stato

Esistono posizioni comuni per lo stato persistente:

Archiviazione lato server

Per la persistenza permanente dei dati che si estende su più utenti e dispositivi, l'app può usare l'archiviazione lato server indipendente a cui si accede tramite un'API Web. Le opzioni includono:

  • Archiviazione BLOB
  • Archiviazione chiave-valore
  • Database relazionale
  • Archiviazione tabelle

Dopo il salvataggio dei dati, lo stato dell'utente viene mantenuto e disponibile in qualsiasi nuova sessione del browser.

Poiché Blazor WebAssembly le app vengono eseguite interamente nel browser dell'utente, richiedono misure aggiuntive per accedere a sistemi esterni sicuri, ad esempio servizi di archiviazione e database. Le app Blazor WebAssembly vengono protette allo stesso modo delle applicazioni a pagina singola. In genere, un'app autentica un utente tramite OAuth/OpenID Connect (OIDC) e quindi interagisce con i servizi di archiviazione e i database tramite chiamate API Web a un'app lato server. L'app lato server media il trasferimento dei dati tra l'app Blazor WebAssembly e il servizio di archiviazione o il database. L'app Blazor WebAssembly mantiene una connessione temporanea all'app lato server, mentre l'app sul lato server ha una connessione permanente all'archiviazione.

Per ulteriori informazioni, vedi le seguenti risorse:

Per altre informazioni sulle opzioni di archiviazione dei dati di Azure, vedere quanto segue:

URL

Per i dati temporanei che rappresentano lo stato di navigazione, modellare i dati come parte dell'URL. Esempi di stato utente modellati nell'URL includono:

  • ID di un'entità visualizzata.
  • Numero di pagina corrente in una griglia di paging.

Il contenuto della barra degli indirizzi del browser viene conservato se l'utente ricarica manualmente la pagina.

Per informazioni sulla definizione dei modelli di URL con la @page direttiva , vedere ASP.NET routing e navigazione coreBlazor.

Archiviazione del browser

Per i dati temporanei che l'utente sta creando attivamente, un percorso di archiviazione comunemente usato è il browser localStorage e sessionStorage le raccolte:

  • localStorage ha come ambito la finestra del browser. Se l'utente ricarica la pagina o si chiude e riapre il browser, lo stato persiste. Se l'utente apre più schede del browser, lo stato viene condiviso tra le schede. I dati vengono mantenuti fino localStorage a quando non vengono cancellati in modo esplicito.
  • sessionStorage è limitato alla scheda del browser. Se l'utente ricarica la scheda, lo stato persiste. Se l'utente chiude la scheda o il browser, lo stato viene perso. Se l'utente apre più schede del browser, ogni scheda ha una propria versione indipendente dei dati.

Nota

localStorage e sessionStorage possono essere usati nelle Blazor WebAssembly app, ma solo scrivendo codice personalizzato o usando un pacchetto di terze parti.

In genere, sessionStorage è più sicuro da usare. sessionStorage evita il rischio che un utente apra più schede e riscontri quanto segue:

  • Bug nell'archiviazione dello stato tra le schede.
  • Comportamento confuso quando una scheda sovrascrive lo stato di altre schede.

localStorage è la scelta migliore se l'app deve mantenere lo stato di chiusura e riaprire il browser.

Avviso

Gli utenti possono visualizzare o manomettere i dati archiviati in localStorage e sessionStorage.

Servizio contenitore dello stato in memoria

I componenti annidati in genere associano i dati usando binding concatenato come descritto in ASP.NET data binding coreBlazor. I componenti annidati e non registrati possono condividere l'accesso ai dati usando un contenitore di stato in memoria registrato. Una classe contenitore di stato personalizzata può usare un oggetto assegnabile Action per notificare ai componenti in parti diverse dell'app modifiche di stato. Nell'esempio seguente :

  • Una coppia di componenti usa un contenitore di stato per tenere traccia di una proprietà.
  • Un componente nell'esempio seguente è annidato nell'altro componente, ma l'annidamento non è necessario per il funzionamento di questo approccio.

Importante

Nell'esempio riportato in questa sezione viene illustrato come creare un servizio contenitore di stato in memoria, registrare il servizio e usare il servizio nei componenti. L'esempio non rende persistenti i dati senza ulteriore sviluppo. Per l'archiviazione permanente dei dati, il contenitore di stato deve adottare un meccanismo di archiviazione sottostante che sopravvive quando la memoria del browser viene cancellata. Questa operazione può essere eseguita con localStorage/sessionStorage o con altre tecnologie.

StateContainer.cs:

public class StateContainer
{
    private string? savedString;

    public string Property
    {
        get => savedString ?? string.Empty;
        set
        {
            savedString = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

App sul lato client (Program file):

builder.Services.AddSingleton<StateContainer>();

App sul lato server (Program file, ASP.NET Core in .NET 6 o versione successiva):

builder.Services.AddScoped<StateContainer>();

App sul lato server (Startup.ConfigureServices di Startup.cs, ASP.NET Core precedenti alla 6.0):

services.AddScoped<StateContainer>();

Shared/Nested.razor:

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the Nested component
    </button>
</p>

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = 
            $"New value set in the Nested component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

StateContainerExample.razor:

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer

<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the State Container Example component
    </button>
</p>

<Nested />

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = "New value set in the State " +
            $"Container Example component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

I componenti precedenti implementano IDisposablee i OnChange delegati vengono annullati nei Dispose metodi, chiamati dal framework quando i componenti vengono eliminati. Per altre informazioni, vedere Ciclo di vita dei componenti di ASP.NET Core Razor.

Approcci aggiuntivi

Quando si implementa l'archiviazione dello stato personalizzata, un approccio utile consiste nell'adottare valori e parametri a catena:

  • Per utilizzare lo stato in molti componenti.
  • Se è presente un solo oggetto di stato di primo livello da rendere persistente.

Risoluzione dei problemi

In un servizio di gestione dello stato personalizzato, un callback richiamato all'esterno del contesto di Blazorsincronizzazione deve eseguire il wrapping della logica del callback in ComponentBase.InvokeAsync per spostarlo nel contesto di sincronizzazione del renderer.

Quando il servizio di gestione dello stato non chiama StateHasChanged il Blazorcontesto di sincronizzazione, viene generato l'errore seguente:

System.InvalidOperationException: 'Il thread corrente non è associato al Dispatcher. Usare InvokeAsync() per passare all'esecuzione del Dispatcher durante l'attivazione del rendering o dello stato del componente".

Per altre informazioni e un esempio di come risolvere questo errore, vedere ASP.NET rendering dei componenti principaliRazor.

Risorse aggiuntive