Dela via


ASP.NET Core Blazor tillståndshantering

Notera

Det här är inte den senaste versionen av den här artikeln. För den aktuella versionen, se .NET 9-versionen av den här artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella utgåvan, se .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen av artikeln finns i .NET 9-versionen av den här artikeln.

Den här artikeln beskriver vanliga metoder för att underhålla en användares data (tillstånd) när de använder en app och mellan webbläsarsessioner.

Noter

Kodexemplen i den här artikeln använder nullbara referenstyper (NRT) och .NET-kompilatorn null-state static analysis, som stöds i ASP.NET Core i .NET 6 eller senare. När du riktar in dig på ASP.NET Core 5.0 eller tidigare tar du bort null-typbeteckningen (?) från typerna i artikelns exempel.

Underhålla användartillstånd

Blazor på serversidan är ett tillståndskänsligt appramverk. För det mesta upprätthåller appen en anslutning till servern. Användarens tillstånd lagras i serverns minne i en krets.

Exempel på användartillstånd som finns i en krets är:

  • Hierarkin för komponentinstanser och deras senaste återgivningsutdata i det renderade användargränssnittet.
  • Värdena för fält och egenskaper i komponentinstanser.
  • Data som lagras i beroendeinmatning (DI) tjänstinstanser som är begränsade till kretsen.

Användartillstånd kan också hittas i JavaScript-variabler i webbläsarens minne via JavaScript-interopanrop.

Om en användare upplever en tillfällig nätverksanslutningsförlust försöker Blazor återansluta användaren till den ursprungliga kretsen med sitt ursprungliga tillstånd. Det är dock inte alltid möjligt att återansluta en användare till den ursprungliga kretsen i serverns minne:

  • Servern kan inte behålla en frånkopplad krets för alltid. Servern måste frigöra en frånkopplad krets efter en timeout eller när servern är under minnesbelastning.
  • I distributionsmiljöer med flera servrar kan enskilda servrar misslyckas eller tas bort automatiskt när de inte längre behövs för att hantera den totala mängden begäranden. De ursprungliga serverbearbetningsbegäranden för en användare kan bli otillgängliga när användaren försöker återansluta.
  • Användaren kan stänga och öppna webbläsaren igen eller läsa in sidan igen, vilket tar bort alla tillstånd som finns i webbläsarens minne. Till exempel går JavaScript-variabelvärden som anges via JavaScript-interop-anrop förlorade.

När en användare inte kan återanslutas till sin ursprungliga krets får användaren en ny krets med ett tomt tillstånd. Detta motsvarar att stänga och öppna en skrivbordsapp igen.

Bibehåll tillstånd över kretsar

I allmänhet upprätthåller du tillstånd över kretsar där användare aktivt skapar data, inte bara läser data som redan finns.

För att bevara tillståndet mellan kretsar måste appen spara data till någon annan lagringsplats än serverns minne. Tillståndsbeständighet är inte automatiskt. Du måste vidta åtgärder när du utvecklar appen för att implementera tillståndskänslig datapersistence.

Data persistens krävs vanligtvis bara för värdefullt tillstånd som användarna har lagt ner ansträngning på att skapa. I följande exempel kan ett beständigt tillstånd spara tid eller underlätta kommersiell verksamhet:

  • Webbformulär i flera steg: Det är tidskrävande för en användare att återigen ange information för flera steg i ett sådant formulär om deras information går förlorad. En användare förlorar tillståndet i det här scenariot om de navigerar bort från formuläret och returnerar senare.
  • Kundvagnar: Alla kommersiellt viktiga komponenter i en app som representerar potentiella intäkter kan bibehållas. En användare som förlorar sin stat, och därmed sin kundvagn, kan köpa färre produkter eller tjänster när de återvänder till webbplatsen senare.

En app kan bara bevara apptillstånd. UIs kan inte bevaras, till exempel komponentinstanser och deras återgivningsträd. Komponenter och återgivningsträd är vanligtvis inte serialiserbara. För att bevara användargränssnittets tillstånd, till exempel de expanderade noderna i en trädvykontroll, måste appen använda anpassad kod för att modellera beteendet för användargränssnittstillståndet som serialiserbart apptillstånd.

Var ska du spara tillstånd

Det finns vanliga platser för att lagra tillstånd:

Lagring på serversidan

För permanent datapersistence som omfattar flera användare och enheter kan appen använda lagring på serversidan. Alternativen är:

  • Bloblagring
  • Nyckel-värde-lagring
  • Relationsdatabas
  • Tabellagring

När data har sparats behålls användarens tillstånd och är tillgängligt i alla nya kretsar.

Mer information om lagringsalternativ för Azure-data finns i följande:

URL

För tillfälliga data som representerar navigeringstillståndet modellerar du data som en del av URL:en. Exempel på användartillstånd som modelleras i URL:en är:

  • ID för en visad entitet.
  • Det aktuella sidnumret i ett rutnät.

Innehållet i webbläsarens adressfält behålls:

  • Om användaren uppdaterar sidan manuellt.
  • Om webbservern blir otillgänglig och användaren tvingas läsa in sidan igen för att ansluta till en annan server.

Information om hur du definierar URL-mönster med @page-direktivet finns i ASP.NET Core Blazor routning och navigering.

Webbläsarlagring

För tillfälliga data som användaren aktivt skapar är en vanlig lagringsplats webbläsarens localStorage och sessionStorage samlingar:

  • localStorage är begränsad till webbläsarens instans. Om användaren läser in sidan igen eller stänger och öppnar webbläsaren igen kvarstår tillståndet. Om användaren öppnar flera webbläsarflikar delas tillståndet mellan flikarna. Data sparas i localStorage tills datan uttryckligen tas bort. localStorage-data för ett dokument som läses in i en "privat" eller "inkognito"-session rensas när den sista "privata" fliken stängs.
  • sessionStorage är begränsad till webbläsarfliken. Om användaren läser in fliken igen bevaras tillståndet. Om användaren stänger fliken eller webbläsaren går tillståndet förlorat. Om användaren öppnar flera webbläsarflikar har varje flik en egen oberoende version av data.

I allmänhet är sessionStorage säkrare att använda. sessionStorage undviker risken att en användare öppnar flera flikar och stöter på följande:

  • Problem med lagring av tillstånd över flikar.
  • Förvirrande beteende när en flik skriver över tillståndet för andra flikar.

localStorage är det bättre valet om appen måste bevara tillståndet över stängning och återöppning av webbläsaren.

Varningar för att använda webbläsarlagring:

  • På samma sätt som för en databas på serversidan är inläsning och sparande av data asynkrona.
  • Den begärda sidan finns inte i webbläsaren under förinläsningen, så lokal lagring är inte tillgänglig under prerendering.
  • Det är rimligt att lagra några kilobyte data för serverbaserade Blazor-appar. Utöver några kilobyte måste du tänka på prestandakonsekvenserna eftersom data läses in och sparas i nätverket.
  • Användare kan visa eller manipulera data. ASP.NET Core Data Protection- kan minska risken. Till exempel använder ASP.NET Core Protected Browser Storage ASP.NET Core Data Protection.

NuGet-paket från tredje part tillhandahåller API:er för att arbeta med localStorage och sessionStorage. Det är värt att överväga att välja ett paket som transparent använder ASP.NET Core Data Protection. Dataskydd krypterar lagrade data och minskar den potentiella risken för manipulering av lagrade data. Om JSON-serialiserade data lagras i oformaterad text kan användarna se data med hjälp av webbläsarutvecklarverktyg och även ändra lagrade data. Att skydda triviala data är inget problem. Till exempel är läsning eller ändring av den lagrade färgen för ett användargränssnittselement inte en betydande säkerhetsrisk för användaren eller organisationen. Undvik att tillåta användare att inspektera eller manipulera känsliga data.

ASP.NET Core Protected Browser Storage

ASP.NET Core Protected Browser Storage utnyttjar ASP.NET Core Data Protection för localStorage och sessionStorage.

Observera

Protected Browser Storage förlitar sig på ASP.NET Core Data Protection och stöds endast för Blazor appar på serversidan.

Varning

Microsoft.AspNetCore.ProtectedBrowserStorage är ett experimentpaket som inte stöds och som inte är avsett för produktionsanvändning.

Paketet är endast tillgängligt för användning i ASP.NET Core 3.1-appar.

Konfiguration

  1. Lägg till en paketreferens till Microsoft.AspNetCore.ProtectedBrowserStorage.

    Not

    Mer information om hur du lägger till paket i .NET-appar finns i artiklarna under Installera och hantera paketArbetsflöde för paketförbrukning (NuGet-dokumentation). Bekräfta rätt paketversioner på NuGet.org.

  2. I filen _Host.cshtml lägger du till följande skript i den avslutande </body>-taggen:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. I Startup.ConfigureServicesanropar du AddProtectedBrowserStorage för att lägga till localStorage och sessionStorage tjänster i tjänstsamlingen:

    services.AddProtectedBrowserStorage();
    

Spara och läsa in data i en komponent

Använd @inject-direktivet för att mata in en instans av något av följande i alla komponenter som kräver inläsning eller sparande av data till webbläsarlagring:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

Valet beror på vilken webbläsarlagringsplats du vill använda. I följande exempel används sessionStorage:

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

@using-direktivet kan placeras i appens _Imports.razor-fil i stället för i komponenten. Med hjälp av _Imports.razor-filen blir namnområdet tillgängligt för större segment i appen eller hela appen.

Om du vill spara värdet för currentCount i Counter-komponenten i en app baserat på Blazor-projektmallenändrar du metoden IncrementCount för att använda ProtectedSessionStore.SetAsync:

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

I större, mer realistiska appar är lagring av enskilda fält ett osannolikt scenario. Appar är mer benägna att lagra hela modellobjekt som innehåller komplext tillstånd. ProtectedSessionStore serialiserar och deserialiserar JSON-data automatiskt för att lagra komplexa tillståndsobjekt.

I föregående kodexempel lagras currentCount data som sessionStorage['count'] i användarens webbläsare. Data lagras inte i oformaterad text utan skyddas i stället med hjälp av ASP.NET Core Data Protection. Krypterade data kan kontrolleras om sessionStorage['count'] utvärderas i webbläsarens utvecklarkonsol.

Om du vill återställa currentCount data om användaren återgår till Counter komponenten senare, inklusive om användaren är på en ny krets, använder du 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");
}

Om komponentens parametrar innehåller navigeringstillstånd, anropar du ProtectedSessionStore.GetAsync och tilldelar ett icke-null-resulterande värde i OnParametersSetAsync, inte OnInitializedAsync. OnInitializedAsync anropas bara en gång när komponenten först instansieras. OnInitializedAsync anropas inte igen senare om användaren navigerar till en annan URL medan den finns kvar på samma sida. Mer information finns i ASP.NET Core Razor-komponentens livscykel.

Varning

Exemplen i det här avsnittet fungerar bara om servern inte har förrendering aktiverat. När förrendering är aktiverad genereras ett fel som förklarar att JavaScript-interoperabilitetsanrop inte kan utfärdas eftersom komponenten förrenderas.

Inaktivera antingen prerendering eller lägg till ytterligare kod för att arbeta med prerendering. Mer information om hur du skriver kod som fungerar med prerendering finns i avsnittet Handle prerendering .

Hantera laddningstillståndet

Eftersom webbläsarlagring används asynkront via en nätverksanslutning finns det alltid en tidsperiod innan data läses in och är tillgängliga för en komponent. För bästa resultat kan du återge ett meddelande medan inläsning pågår i stället för att visa tomma data eller standarddata.

En metod är att spåra om datan är null, vilket innebär att datan fortfarande läses in. I standardkomponenten Counter lagras antalet i en int. Gör currentCount nullbar genom att lägga till ett frågetecken (?) i typen (int):

private int? currentCount;

Istället för att villkorslöst visa antalet och Increment-knappen, ska du visa dessa element endast om datan läses in genom att kontrollera HasValue:

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

Hantera prerendering

Under förberäknande:

  • Det finns ingen interaktiv anslutning till användarens webbläsare.
  • Webbläsaren har ännu ingen sida där den kan köra JavaScript-kod.

localStorage eller sessionStorage är inte tillgängliga under förberedande rendering. Om komponenten försöker interagera med lagring genereras ett fel som förklarar att JavaScript-interop-anrop inte kan utfärdas eftersom komponenten förinstalleras.

Ett sätt att lösa felet är att inaktivera prerendering. Detta är vanligtvis det bästa valet om appen använder webbläsarbaserad lagring. Prerendering lägger till ytterligare komplexitet och gynnar inte appen eftersom appen inte kan rendera i förväg något användbart innehåll förrän localStorage eller sessionStorage är tillgängliga.

Om du vill inaktivera förrendering anger du återgivningsläget med parametern prerender inställd på false på den högsta komponenten i appens komponenthierarki som inte är en rotkomponent.

Not

Det går inte att göra en rotkomponent interaktiv, till exempel komponenten App. Därför kan prerendering inte inaktiveras direkt av komponenten App.

För appar som baseras på Blazor Web App projektmall inaktiveras vanligtvis prerendering där den Routes komponenten används i komponenten App (Components/App.razor):

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

Inaktivera även prerendering för den HeadOutlet komponenten:

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

För mer information, se ASP.NET Core Blazor renderingslägen.

Om du vill inaktivera förrendering öppnar du filen _Host.cshtml och ändrar attributet render-mode för -komponenttaggen till Server:

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

När förrendering är inaktiverat, är förrendering av <head> innehåll inaktiverat.

Prerendering kan vara användbart för andra sidor som inte använder localStorage eller sessionStorage. Om du vill behålla förrendering skjuter du upp inläsningsåtgärden tills webbläsaren är ansluten till kretsen. Följande är ett exempel på hur du lagrar ett räknarvärde:

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

Ta hänsyn till tillståndsbevarande till en vanlig provider

Om många komponenter förlitar sig på webbläsarbaserad lagring skapar implementering av tillståndsproviderkod många gånger kodduplicering. Ett alternativ för att undvika kodduplicering är att skapa en överordnad komponent för tillståndsprovidern som kapslar in tillståndsproviderlogik. Barnkomponenter kan arbeta med beständiga data utan hänsyn till mekanismen för beständigt tillstånd.

I följande exempel på en CounterStateProvider-komponent sparas räknardata i sessionStorage, och den hanterar inläsningsfasen genom att inte återge sitt underordnade innehåll förrän inläsningen av tillståndet har slutförts.

Komponenten CounterStateProvider hanterar prerendering genom att inte läsa in tillstånd förrän efter komponentrendering i OnAfterRenderAsync livscykelmetod, som inte körs under prerendering.

Metoden i det här avsnittet kan inte utlösa återrendering av flera prenumerationskomponenter på samma sida. Om en prenumererad komponent ändrar tillståndet, renderar den igen och kan visa det uppdaterade tillståndet, men en annan komponent på samma sida som visar detta tillstånd visar föråldrad data tills den själv renderas om nästa gång. Därför passar metoden som beskrivs i det här avsnittet bäst för att använda tillstånd i en enskild komponent på sidan.

CounterStateProvider.razor:

@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 OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

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

    public async Task IncrementCount()
    {
        CurrentCount++;
        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 OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isLoaded = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

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

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

Anteckning

Mer information om RenderFragmentfinns i ASP.NET Core Razor-komponenter.

Om du vill göra tillståndet tillgängligt för alla komponenter i en app omsluter du CounterStateProvider-komponenten runt Router (<Router>...</Router>) i komponenten Routes med global interaktiv återgivning på serversidan (interaktiv SSR).

I komponenten App (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

I komponenten Routes (Components/Routes.razor):

Om du vill använda komponenten CounterStateProvider omsluter du en instans av komponenten runt alla andra komponenter som kräver åtkomst till räknartillståndet. Om du vill göra tillståndet tillgängligt för alla komponenter i en app omsluter du CounterStateProvider-komponenten runt Router i komponenten App (App.razor):

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

Notera

Med versionen av ASP.NET Core 5.0.1 och för ytterligare 5.x-versioner innehåller komponenten Router parametern PreferExactMatches inställd på @true. Mer information finns i Migrera från ASP.NET Core 3.1 till 5.0.

Inslutna komponenter tar emot och kan ändra det sparade räknartillståndet. Följande Counter komponent implementerar mönstret:

@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)
        {
            await CounterStateProvider.IncrementCount();
        }
    }
}

Den föregående komponenten är inte nödvändig för att interagera med ProtectedBrowserStorageoch den hanterar inte heller en "inläsningsfas".

Generellt rekommenderas mönstret för tillståndsprovider där är en överordnad komponent åt.

  • Hantera tillståndet över många komponenter.
  • Om det bara finns ett tillståndsobjekt på den översta nivån att bevara.

Om du vill bevara många olika tillståndsobjekt och använda olika delmängder av objekt på olika platser är det bättre att undvika att bevara tillståndet globalt.

Användartillståndet som skapas i en Blazor WebAssembly app lagras i webbläsarens minne.

Exempel på användartillstånd som finns i webbläsarens minne är:

  • Hierarkin för komponentinstanser och deras senaste återgivningsutdata i det renderade användargränssnittet.
  • Värdena för fält och egenskaper i komponentinstanser.
  • Data som lagras i beroendeinmatning (DI) tjänstinstanser.
  • Värden som anges via JavaScript-interop--anrop.

När en användare stänger och öppnar webbläsaren igen eller läser in sidan igen går användartillståndet i webbläsarens minne förlorat.

Anteckning

Protected Browser Storage (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage namnområde) förlitar sig på ASP.NET Core Data Protection och stöds endast för Blazor appar på serversidan.

Behåll tillstånd mellan webbläsarsessioner

I allmänhet upprätthåller du tillstånd mellan webbläsarsessioner där användare aktivt skapar data, inte bara läser data som redan finns.

För att bevara tillståndet mellan webbläsarsessioner måste appen spara data till någon annan lagringsplats än webbläsarens minne. Tillståndsbeständighet är inte automatiskt. Du måste vidta åtgärder när du utvecklar appen för att implementera tillståndskänslig datapersistence.

Datas uthållighet krävs vanligtvis bara för värdefullt tillstånd som användarna har ansträngt sig för att skapa. I följande exempel innebär ett beständigt tillstånd antingen att spara tid eller att underlätta kommersiella aktiviteter:

  • Webbformulär i flera steg: Det är tidskrävande för en användare att mata in data på nytt för flera slutförda steg i ett sådant formulär om informationen tappas bort. En användare förlorar tillståndet i det här scenariot om de navigerar bort från formuläret och returnerar senare.
  • Kundvagnar: Alla kommersiellt viktiga komponenter i en app som representerar potentiella intäkter kan bibehållas. En användare som förlorar sin stat, och därmed sin kundvagn, kan köpa färre produkter eller tjänster när de återvänder till webbplatsen senare.

En app kan bara bevara apptillstånd. UIs kan inte bevaras, till exempel komponentinstanser och deras återgivningsträd. Komponenter och återgivningsträd är vanligtvis inte serialiserbara. För att bevara användargränssnittets tillstånd, till exempel de expanderade noderna i en trädvykontroll, måste appen använda anpassad kod för att modellera beteendet för användargränssnittstillståndet som serialiserbart apptillstånd.

Var ska du spara tillstånd

Det finns vanliga platser för att bevara tillstånd:

Lagring på serversidan

För permanent datapersistence som omfattar flera användare och enheter kan appen använda oberoende lagring på serversidan som nås via ett webb-API. Alternativen är:

  • Bloblagring
  • Nyckelvärdelagring
  • Relationsdatabas
  • Tabellagring

När data har sparats behålls användarens tillstånd och är tillgängligt i alla nya webbläsarsessioner.

Eftersom Blazor WebAssembly appar körs helt i användarens webbläsare behöver de ytterligare åtgärder för att få åtkomst till säkra externa system, till exempel lagringstjänster och databaser. Blazor WebAssembly appar skyddas på samma sätt som ensidesprogram (SPA). Vanligtvis autentiserar en app en användare via OAuth/OpenID Connect (OIDC) och interagerar sedan med lagringstjänster och databaser via webb-API-anrop till en app på serversidan. Appen på serversidan förmedlar överföring av data mellan Blazor WebAssembly-appen och lagringstjänsten eller databasen. Blazor WebAssembly-appen upprätthåller en tillfällig anslutning till appen på serversidan, medan appen på serversidan har en beständig anslutning till lagringen.

Mer information finns i följande resurser:

Mer information om lagringsalternativ för Azure-data finns i följande:

URL

För tillfälliga data som representerar navigeringstillståndet modellerar du data som en del av URL:en. Exempel på användartillstånd som modelleras i URL:en är:

  • ID för en visad entitet.
  • Det aktuella sidnumret i en paginerad tabell.

Innehållet i webbläsarens adressfält behålls om användaren läser in sidan igen manuellt.

Information om hur du definierar URL-mönster med @page-direktivet finns i ASP.NET Core Blazor routning och navigering.

Webbläsarlagring

För tillfälliga data som användaren aktivt skapar är en vanlig lagringsplats webbläsarens localStorage och sessionStorage samlingar:

  • localStorage är begränsad till webbläsarens instans. Om användaren läser in sidan igen eller stänger och öppnar webbläsaren igen kvarstår tillståndet. Om användaren öppnar flera webbläsarflikar delas tillståndet mellan flikarna. Data sparas i localStorage tills de uttryckligen rensas. localStorage-datan för dokumentet som laddats i ett "privat fönster" eller "inkognitoläge" rensas när den sista fliken med "privat" läge stängs.
  • sessionStorage är begränsad till webbläsarfliken. Om användaren läser in fliken igen bevaras tillståndet. Om användaren stänger fliken eller webbläsaren går tillståndet förlorat. Om användaren öppnar flera webbläsarflikar har varje flik en egen oberoende version av data.

Notera

localStorage och sessionStorage kan användas i Blazor WebAssembly appar, men bara genom att skriva anpassad kod eller använda ett paket från tredje part.

I allmänhet är sessionStorage säkrare att använda. sessionStorage undviker risken att en användare öppnar flera flikar och stöter på följande:

  • Bugg i statuslagring över flikar.
  • Förvirrande beteende när en flik skriver över tillståndet för andra flikar.

localStorage är det bättre valet om appen måste bevara tillståndet över stängning och återöppning av webbläsaren.

Varning

Användare kan visa eller manipulera data som lagras i localStorage och sessionStorage.

Containertjänst för minnesinternt tillstånd

Kapslade komponenter binder vanligtvis data med hjälp av länkad bindning enligt beskrivningen i ASP.NET Core Blazor-databindning. Kapslade och ej kapslade komponenter kan dela åtkomst till data med hjälp av en registrerad tillståndscontainer i minnet. En containerklass för anpassat tillstånd kan använda en tilldelningsbar Action för att meddela komponenter i olika delar av appen om tillståndsändringar. I följande exempel:

  • Ett par komponenter använder en tillståndscontainer för att spåra en egenskap.
  • En komponent i följande exempel är kapslad i den andra komponenten, men kapsling krävs inte för att den här metoden ska fungera.

Viktig

Exemplet i det här avsnittet visar hur du skapar en containertjänst för minnesinternt tillstånd, registrerar tjänsten och använder tjänsten i komponenter. Exemplet bevarar inte data utan vidare utveckling. För beständig lagring av data måste tillståndscontainern använda en underliggande lagringsmekanism som överlever när webbläsarminnet rensas. Detta kan åstadkommas med localStorage/sessionStorage eller någon annan teknik.

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

Appar på klientsidan (Program fil):

builder.Services.AddSingleton<StateContainer>();

Appar på serversidan (Program fil, ASP.NET Core i .NET 6 eller senare):

builder.Services.AddScoped<StateContainer>();

Appar på serversidan (Startup.ConfigureServices av Startup.cs, ASP.NET Core tidigare än 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;
    }
}

De föregående komponenterna implementerar IDisposable, och de OnChange ombuden avregistreras i de Dispose metoderna, som anropas av ramverket när komponenterna tas bort. Mer information finns i ASP.NET Core Razor-komponentens livscykel.

Ytterligare metoder

När du implementerar anpassad tillståndslagring är en användbar metod att använda sammanhängande värden och parametrar:

  • För att använda tillstånd över flera komponenter.
  • Om det bara finns ett tillståndsobjekt på den översta nivån att bevara.

Felsöka

När du använder en anpassad tillståndshanteringstjänst där du vill stötta tillståndsändringar utanför Blazor:s synkroniseringskontext (till exempel från en timer eller en bakgrundstjänst) måste alla konsumerande komponenter omsluta StateHasChanged-anropet i ComponentBase.InvokeAsync. Detta säkerställer att ändringsmeddelandet hanteras i återgivarens synkroniseringskontext.

När tillståndshanteringstjänsten inte anropar StateHasChanged på Blazorsynkroniseringskontext utlöses följande fel:

System.InvalidOperationException: "Den aktuella tråden är inte associerad med Dispatcher. Använd InvokeAsync() för att växla körning till Dispatcher när du triggar återgivning eller komponenttillstånd.

Mer information och ett exempel på hur du åtgärdar det här felet finns i ASP.NET Core Razor komponentåtergivning.

Ytterligare resurser