Blazor-Zustandsverwaltung in ASP.NET Core

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

In diesem Artikel werden allgemeine Ansätze zum Speichern von Benutzerdaten (Status) beschrieben, die bei der Verwendung einer App und sitzungsübergreifend angewendet werden können.

Hinweis

Die Codebeispiele in diesem Artikel verwenden Nullwerte zulassende Verweistypen (Nullable Reference Types, NRTs) und die statische Analyse des NULL-Zustands des .NET-Compilers, die in ASP.NET Core in .NET 6 oder höher unterstützt werden. Entfernen Sie bei ASP.NET Core 5.0 oder früher die NULL-Typbezeichnung (?) aus den Typen in den Beispielen des Artikels.

Verwalten des Benutzerstatus

Serverseitiges Blazor ist ein zustandsbehaftetes App-Framework. In den meisten Fällen unterhält die App eine Verbindung mit dem Server. Der Benutzerzustand wird in einer Verbindung im Speicher des Servers gespeichert.

Beispiele für den in einer Verbindung gespeicherten Benutzerzustand sind:

  • Die Hierarchie der Komponenteninstanzen und deren neueste Renderausgabe in der gerenderten Benutzeroberfläche.
  • Die Werte der Felder und Eigenschaften in Komponenteninstanzen.
  • Daten, die in -Abhängigkeitsinjektion (DI)-Dienstinstanzen gespeichert sind, die auf die Verbindung beschränkt sind.

Der Benutzerzustand ist möglicherweise auch in JavaScript-Variablen im Arbeitsspeicher des Browsers enthalten, die über JavaScript-Interop-Aufrufe festgelegt werden.

Wenn die Netzwerkverbindung vorübergehend getrennt wird, versucht Blazor, den Benutzer nochmals mit der ursprünglichen Verbindung mit seinem ursprünglichen Zustand zu verbinden. Es ist jedoch nicht immer möglich, einen Benutzer noch mal mit der ursprünglichen Verbindung im Arbeitsspeicher des Servers zu verbinden:

  • Der Server kann eine getrennte Verbindung nicht dauerhaft beibehalten. Der Server muss eine getrennte Verbindung nach einem Timeout freigeben, oder wenn der Server nicht über genügend Arbeitsspeicher verfügt.
  • In Bereitstellungsumgebungen mit mehreren Servern und Lastenausgleich tritt bei einzelnen Servern möglicherweise ein Fehler auf, oder sie werden automatisch entfernt, wenn nicht mehr das gesamte Anforderungsvolumen verarbeitet werden muss. Die ursprünglichen Serververarbeitungsanforderungen für einen Benutzer sind möglicherweise nicht mehr verfügbar, wenn der Benutzer versucht, erneut eine Verbindung herzustellen.
  • Der Benutzer kann den Browser schließen und erneut öffnen oder die Seite neu laden, wodurch der im Arbeitsspeicher des Browsers enthaltene Zustand entfernt wird. So gehen beispielsweise JavaScript-Variablenwerte, die durch JavaScript-Interop-Aufrufe festgelegt wurden, verloren.

Wenn ein Benutzer nicht erneut mit der ursprünglichen Verbindung verbunden werden kann, erhält der Benutzer eine neue Verbindung mit einem leeren Zustand. Dies entspricht dem Schließen und erneuten Öffnen einer Desktop-App.

Persistentes verbindungsübergreifendes Speichern des Zustands

Im Allgemeinen gilt: Die Beibehaltung des Zustands über Verbindungen hinweg erfolgt in Szenarien, in denen Benutzer aktiv Daten erstellen und nicht nur Daten lesen, die bereits vorhanden sind.

Um den Zustand über Verbindungen hinweg beizubehalten, muss die App die Daten an einem anderen Speicherort als im Arbeitsspeicher des Servers persistent speichern. Die Zustandspersistenz erfolgt nicht automatisch. Sie müssen bei der Entwicklung der App Schritte ausführen, um zustandsbehaftete Datenpersistenz zu implementieren.

Die Datenpersistenz ist in der Regel nur für Zustände mit hohem Wert erforderlich, die von Benutzern mit großem Aufwand erstellt wurden. In den folgenden Beispielen werden durch die Beibehaltung des Zustands Zeit- oder Hilfsmitteleinsparungen in kommerziellen Aktivitäten erzielt:

  • Webformulare mit mehreren Schritten: Es ist für den Benutzer zeitaufwändig, Daten für mehrere abgeschlossene Schritte eines mehrstufigen Webformulars wiederholt einzugeben, wenn der Zustand nicht mehr vorhanden ist. Der Benutzer verliert in diesem Szenario seinen Zustand, wenn er vom Formular weg navigiert und später zurückkehrt.
  • Warenkörbe: Kommerziell wichtige Komponenten einer App, die potenzielle Umsätze ermöglichen, können beibehalten werden. Ein Benutzer, der seinen Zustand verliert und dessen Warenkorb dadurch gelöscht wird, kann weniger Produkte oder Dienste kaufen, wenn er zu einem späteren Zeitpunkt zur Website zurückkehrt.

Eine App kann nur den App-Zustand beibehalten. Benutzeroberflächen wie Komponenteninstanzen und deren Renderingstrukturen können nicht beibehalten werden. Komponenten und Renderingstrukturen sind in der Regel nicht serialisierbar. Um den Benutzeroberflächenzustand (z. B. von den erweiterten Knoten einer Strukturansicht) zu erhalten, muss die App über benutzerdefinierten Code verfügen, um das Verhalten des Benutzeroberflächenzustands als serialisierbaren App-Zustand modellieren zu können.

Speicherort des Zustands

Es gibt allgemeine Speicherorte für die Beibehaltung des Zustands:

Serverseitige Speicherung

Für permanente Datenspeicherung, die mehrere Benutzer und Geräte umfasst, kann die App serverseitigen Speicher verwenden. Folgende Optionen sind verfügbar:

  • Blob-Speicher
  • Schlüssel-Wert-Speicher
  • Relationale Datenbank
  • Table Storage

Nachdem die Daten gespeichert wurden, wird der Zustand des Benutzers beibehalten und ist in jeder neuen Verbindung verfügbar.

Weitere Informationen zu den Azure-Datenspeicheroptionen finden Sie hier:

URL

Modellieren Sie vorübergehende Daten, die den Navigationszustand darstellen, als Teil der URL. Beispiele für in der URL modellierte Benutzerzustände:

  • Die ID einer angezeigten Entität.
  • Die aktuelle Seitenzahl in einem ausgelagerten Raster.

Der Inhalt der Adressleiste des Browsers wird in folgenden Fällen beibehalten:

  • Der Benutzer lädt die Seite nochmals manuell.
  • Wenn der Webserver nicht mehr verfügbar ist, ist der Benutzer gezwungen, die Seite nochmals zu laden, damit eine Verbindung mit einem anderen Server hergestellt werden kann.

Informationen zur Definition von URL-Mustern mit der Anweisung @page finden Sie unter ASP.NET Core: Routing und Navigation in Blazor.

Browserspeicherung

Für vorübergehende Daten, die der Benutzer aktiv erstellt, sind die localStorage- und sessionStorage-Sammlungen des Browsers ein häufig verwendeter Speicherort:

  • localStorage bezieht sich auf das Fenster des Browsers. Wenn der Benutzer die Seite neu lädt oder den Browser schließt und erneut öffnet, wird der Zustand beibehalten. Wenn der Benutzer mehrere Browserregisterkarten öffnet, wird der Zustand auf allen Registerkarten freigegeben. Daten bleiben in localStorage so lange beibehalten, bis sie explizit gelöscht werden.
  • sessionStorage bezieht sich auf die Registerkarte des Browsers. Wenn der Benutzer die Registerkarte noch mal lädt, wird der Zustand beibehalten. Wenn der Benutzer die Registerkarte oder den Browser schließt, geht der Zustand verloren. Wenn der Benutzer mehrere Browserregisterkarten öffnet, hat jede Registerkarte eine eigene unabhängige Version der Daten.

Im Allgemeinen ist sessionStorage in der Anwendung sicherer. sessionStorage verhindert, dass ein Benutzer mehrere Registerkarten öffnet und auf folgende Probleme stößt:

  • Fehler in Zustandsspeicher über mehrere Registerkarten hinweg
  • Verwirrendes Verhalten, wenn eine Registerkarte den Zustand anderer Registerkarten überschreibt

localStorage ist die bessere Wahl, wenn der Zustand der App auch nach dem Schließen und erneuten Öffnen des Browsers bestehen bleiben muss.

Einschränkungen bei der Verwendung des Browserspeichers:

  • Ähnlich wie bei der Verwendung einer serverseitigen Datenbank erfolgt das Laden und Speichern von Daten asynchron.
  • Im Gegensatz zu einer serverseitigen Datenbank ist der Speicher während des Prerenderings nicht verfügbar, da die angeforderte Seite im Browser während der Prerenderingphase nicht vorhanden ist.
  • Die Speicherung einiger Kilobyte-Daten ist für serverseitige Blazor-Apps angemessen. Bei mehr Kilobyte müssen Beeinträchtigungen der Leistung berücksichtigt werden, da die Daten über das Netzwerk geladen und gespeichert werden.
  • Benutzer können die Daten anzeigen oder manipulieren. Die Datenschutzlösung von ASP.NET Core reduziert die damit verbundenen Risiken. Beispielsweise verwendet ASP.NET Core Protected Browser Storage ASP.NET Core-Datenschutz.

NuGet-Pakete von Drittanbietern stellen APIs für die Verwendung mit localStorage und sessionStorage bereit. Erwägen Sie die Auswahl eines Pakets, das die Datenschutzlösung von ASP.NET Core transparent verwendet. Die Datenschutzlösung verschlüsselt gespeicherte Daten und verringert das potenzielle Risiko, dass gespeicherte Daten manipuliert werden. Wenn JSON-serialisierte Daten als Nur-Text gespeichert werden, können Benutzer die Daten mithilfe von Browserentwicklertools anzeigen und auch die gespeicherten Daten ändern. Das Sichern von Daten ist nicht immer problematisch, da die Daten möglicherweise trivial sind. Beispielsweise stellt das Lesen oder Ändern der gespeicherten Farbe eines Benutzeroberflächenelements für den Benutzer oder die Organisation kein ernsthaftes Sicherheitsrisiko dar. Vermeiden Sie, dass Benutzer sensible Daten überprüfen oder manipulieren können.

ASP.NET Core Protected Browser Storage

ASP.NET Core Protected Browser Storage nutzt ASP.NET Core-Datenschutz für localStorage und sessionStorage.

Hinweis

Der geschützte Browserspeicher basiert auf ASP.NET Core-Datenschutz und wird nur für serverseitige Blazor-Apps unterstützt.

Warnung

Microsoft.AspNetCore.ProtectedBrowserStorage ist ein nicht unterstütztes, experimentelles Paket, das nicht für den Einsatz in der Produktion gedacht ist.

Das Paket ist nur für die Verwendung in ASP.NET Core 3.1 -Apps verfügbar.

Konfiguration

  1. Fügen Sie Microsoft.AspNetCore.ProtectedBrowserStorage einen Paketverweis hinzu.

    Hinweis

    Einen Leitfaden zum Hinzufügen von Paketen zu .NET-Apps finden Sie in Installieren und Verwalten von Paketen unter Workflow der Nutzung von Paketen (NuGet-Dokumentation). Überprüfen Sie unter NuGet.org, ob die richtige Paketversion verwendet wird.

  2. Fügen Sie in der Datei _Host.cshtml das folgende Skript innerhalb des schließenden Tags </body> hinzu:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. Rufen Sie in Startup.ConfigureServicesAddProtectedBrowserStorage auf, um der Dienstsammlung die Dienste localStorage und sessionStorage hinzuzufügen:

    services.AddProtectedBrowserStorage();
    

Speichern und Laden von Daten in einer Komponente

Verwenden Sie für jede Komponente, die das Laden oder Speichern von Daten im Browserspeicher erfordert, die @inject-Anweisung, um eine Instanz von einem der folgenden Komponenten einzufügen:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

Die Auswahl hängt davon ab, welchen Browserspeicherort Sie verwenden möchten. Im folgenden Beispiel wird sessionStorage verwendet:

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

Die @using-Anweisung kann in einer Datei _Imports.razor der App anstatt in der Komponente platziert werden. Bei Verwendung der _Imports.razor-Datei wird der Namespace für größere Segmente der App oder die gesamte App zur Verfügung gestellt.

Um den currentCount-Wert in der Counter-Komponente einer App beizubehalten, die auf der Blazor-Projektvorlage basiert, ändern Sie die IncrementCount-Methode so, dass ProtectedSessionStore.SetAsync verwendet wird:

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

Bei größeren, realistischeren Apps ist die Speicherung einzelner Felder ein unwahrscheinliches Szenario. Apps speichern meist ganze Modellobjekte, die komplexe Zustände enthalten. ProtectedSessionStore serialisiert und deserialisiert JSON-Daten automatisch, um komplexe Zustandsobjekte zu speichern.

Im vorangehenden Codebeispiel werden die currentCount-Daten als sessionStorage['count'] im Browser des Benutzers gespeichert. Die Daten werden nicht als Nur-Text gespeichert, sondern mit der Datenschutzlösung von ASP.NET Core geschützt. Die verschlüsselten Daten können untersucht werden, wenn sessionStorage['count'] in der Entwicklerkonsole des Browsers ausgewertet wird.

Verwenden Sie ProtectedSessionStore.GetAsync, um die currentCount-Daten wiederherzustellen, wenn der Benutzer zu einem späteren Zeitpunkt zur Counter-Komponente zurückkehrt (auch wenn er sich in einer neuen Verbindung befindet):

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

Wenn die Parameter der Komponente den Navigationszustand enthalten, rufen Sie ProtectedSessionStore.GetAsync auf, und weisen Sie ein Ergebnis ungleich null in OnParametersSetAsync zu, nicht OnInitializedAsync. OnInitializedAsync wird nur ein Mal aufgerufen, wenn die Komponente zum ersten Mal instanziiert wird. OnInitializedAsync wird später nicht nochmals aufgerufen, wenn der Benutzer zu einer anderen URL navigiert, während er auf der gleichen Seite bleibt. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Warnung

Die Beispiele in diesem Abschnitt funktionieren nur, wenn für den Server kein Prerendering aktiviert ist. Bei aktiviertem PreRendering wird ein Fehler generiert, der besagt, dass JavaScript-Interop-Aufrufe nicht ausgegeben werden können, weil PreRendering für die Komponente ausgeführt wird.

Deaktivieren Sie entweder Prerendering, oder fügen Sie zusätzlichen Code hinzu, um mit Prerendering zu arbeiten. Weitere Informationen zum Schreiben von Code, der mit Prerendering kompatibel ist, finden Sie im Abschnitt Handhabung von Prerendering.

Handhabung des Ladezustands

Da die Browserspeicherung asynchron über eine Netzwerkverbindung erfolgt, vergeht immer eine gewisse Zeit, bis die Daten geladen werden und für eine Komponente zur Verfügung stehen. Die besten Ergebnisse erzielen Sie, wenn Sie während des Ladevorgangs eine Meldung rendern, anstatt leere oder Standarddaten anzuzeigen.

Ein Ansatz besteht darin, nachzuverfolgen, ob die Daten den Zustand null aufweisen. Dies bedeutet, dass die Daten noch geladen werden. In der Counter-Standardkomponente wird die Anzahl in einer int gespeichert. Legen Sie fest, dass currentCount NULL-Werte aufweisen kann, indem Sie dem Typ (int) ein Fragezeichen (?) hinzufügen:

private int? currentCount;

Anstatt bedingungslos die Anzahl und die Schaltfläche Increment anzuzeigen, zeigen Sie diese Elemente nur dann an, wenn die Daten geladen wurden, indem Sie HasValue überprüfen:

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

Handhabung von Prerendering

Während des Prerenderings:

  • Eine interaktive Verbindung zum Browser des Benutzers ist nicht vorhanden.
  • Der Browser verfügt noch nicht über eine Seite, auf der der JavaScript-Code ausgeführt werden kann.

localStorage oder sessionStorage sind während des Prerenderings nicht verfügbar. Wenn die Komponente versucht,mit dem Speicher zu interagieren, wird ein Fehler generiert, der besagt, dass JavaScript-Interop-Aufrufe nicht ausgegeben werden können, weil PreRendering für die Komponente ausgeführt wird.

Eine Möglichkeit, den Fehler zu beheben, besteht darin, das Prerendering zu deaktivieren. Dies ist normalerweise die beste Lösung, wenn die App den browserbasierten Speicher stark nutzt. Das Prerendering erhöht die Komplexität und bringt der App keinen Nutzen, da die App keinen nützlichen Inhalt vorab rendern kann, bevor nicht localStorage oder sessionStorage verfügbar sind.

Um das Vorabrendering zu deaktivieren, geben Sie den Renderingmodus an, indem Sie den prerender-Parameter auf false die höchste Komponente in der Komponentenhierarchie der Anwendung setzen, die keine Stammkomponente ist.

Hinweis

Die Interaktivität einer Stammkomponente, wie z. B. der App-Komponente, wird nicht unterstützt. Daher kann die Voreinstellung nicht direkt von der App-Komponente deaktiviert werden.

Bei Anwendungen, die auf der Blazor-Webanwendungs-Projektvorlage basieren, wird die Vorabeinstellung in der Regel deaktiviert, wobei die-Routes Komponente in der App-Komponente (Components/App.razor) verwendet wird:

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

Deaktivieren Sie außerdem das Vorabrendering für die HeadOutlet-Komponente:

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

Weitere Informationen finden Sie unter ASP.NET CoreBlazor-Rendermodi.

Öffnen Sie die Datei _Host.cshtml, und ändern Sie das render-mode-Attribut des Komponententag-Hilfsprogramms in Server, um PreRendering zu deaktivieren:

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

Wenn das Prerendering deaktiviert ist, ist das Prerendering von <head>-Inhalten deaktiviert.

Das Prerendering ist möglicherweise nützlich für andere Seiten, die localStorage oder sessionStorage nicht verwenden. Damit PreRendering beibehalten wird, verschieben Sie den Ladevorgang, bis der Browser mit der Verbindung verbunden ist. Im Folgenden finden Sie ein Beispiel für das Speichern eines Zählerwerts:

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

Ausklammern der Zustandsbeibehaltung an einen allgemeinen Speicherort

Wenn viele Komponenten auf die browserbasierte Speicherung zurückgreifen, führt das Implementieren des Zustandsanbietercodes häufig zur Duplizierung des Codes. Eine Möglichkeit, die Duplizierung von Code zu vermeiden, besteht darin, eine übergeordnete Komponente des Zustandsanbieters zu erstellen, die die Zustandsanbieterlogik kapselt. Untergeordnete Komponenten können ohne Berücksichtigung des Zustandspersistenzmechanismus mit beibehaltenen Daten arbeiten.

Im folgenden Beispiel sehen Sie eine CounterStateProvider-Komponente, für die die Zählerdaten in sessionStorage persistent gespeichert werden:

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

Hinweis

Weitere Informationen zu RenderFragment finden Sie unter ASP.NET Core Razor Komponenten.

Die CounterStateProvider-Komponente verarbeitet die Ladephase, indem der untergeordnete Inhalt erst nach Abschluss des Zustandsladevorgangs gerendert wird.

Um den Zustand für alle Komponenten in einer App zugänglich zu machen, umschließen Sie die CounterStateProvider-Komponente um den Router (<Router>...</Router>) in der Routes-Komponente mit globalem interaktivem serverseitigem Rendering (interaktivem SSR).

In der App-Komponente (Components/App.razor):

<Routes @rendermode="InteractiveServer" />

In der Routes-Komponente (Components/Routes.razor):

Um die CounterStateProvider-Komponente zu verwenden, umschließen Sie eine Instanz der Komponente in einer beliebigen anderen Komponente, die Zugriff auf den Zählerzustand benötigt. Um den Zustand für alle Komponenten in einer App zugänglich zu machen, umschließen Sie die CounterStateProvider-Komponente um den Router in der App-Komponente (App.razor):

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

Hinweis

Seit der Veröffentlichung von ASP.NET Core 5.0.1 und allen weiteren 5.x-Releases enthält die Router-Komponente den PreferExactMatches-Parameter, der auf @true festgelegt ist. Weitere Informationen finden Sie unter Migrieren von ASP.NET Core 3.1 zu 5.0.

Umschließende Komponenten erhalten den beibehaltenen Zählerzustand und können diesen ändern. Die folgende Counter-Komponente implementiert das Muster:

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

Die vorherige Komponente muss weder mit ProtectedBrowserStorage interagieren, noch eine „Ladephase“ verarbeiten.

Für eine Handhabung des Prerenderings wie oben beschrieben kann CounterStateProvider dahingehend geändert werden, dass alle Komponenten, die Zählerdaten verbrauchen, automatisch mit dem Prerendering arbeiten. Weitere Informationen finden Sie im Abschnitt Verarbeiten von PreRendering.

In folgenden Fällen wird im Allgemeinen das Muster der übergeordneten Komponente des Zustandsanbieters empfohlen:

  • Um den Zustand über viele Komponenten hinweg zu nutzen.
  • Es muss nur ein Zustandsobjekt auf der obersten Ebene beibehalten werden.

Um viele verschiedene Zustandsobjekte beizubehalten und verschiedene Teilmengen von Objekten an verschiedenen Orten zu verarbeiten, ist es besser, das globale persistente Speichern des Zustands zu vermeiden.

Der Benutzerzustand, der in einer Blazor WebAssembly-App erstellt wird, wird im Arbeitsspeicher des Browsers gespeichert.

Beispiele für den Benutzerzustand im Arbeitsspeicher des Browsers sind:

  • Die Hierarchie der Komponenteninstanzen und deren neueste Renderausgabe in der gerenderten Benutzeroberfläche.
  • Die Werte der Felder und Eigenschaften in Komponenteninstanzen.
  • Daten, die in DI-Dienstinstanzen (Dependency Injection, Abhängigkeitsinjektion) gespeichert sind.
  • Werte, die durch JavaScript-Interop-Aufrufe festgelegt werden.

Wenn ein Benutzer seinen Browser schließt und erneut öffnet oder die Seite neu lädt, geht der Benutzerzustand im Arbeitsspeicher des Browsers verloren.

Hinweis

Geschützter Browserspeicher (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage Namespace) basiert auf ASP.NET Core-Datenschutz und wird nur für serverseitige Blazor-Apps unterstützt.

Persistentes Speichern des Zustands über Browsersitzungen hinweg

Im Allgemeinen gilt: Die Beibehaltung des Zustands über mehrere Browsersitzungen hinweg erfolgt in Szenarien, in denen Benutzer aktiv Daten erstellen und nicht nur Daten lesen, die bereits vorhanden sind.

Um den Zustand über Browsersitzungen hinweg beizubehalten, muss die App die Daten an einem anderen Speicherort als im Arbeitsspeicher des Browsers persistent speichern. Die Zustandspersistenz erfolgt nicht automatisch. Sie müssen bei der Entwicklung der App Schritte ausführen, um zustandsbehaftete Datenpersistenz zu implementieren.

Die Datenpersistenz ist in der Regel nur für Zustände mit hohem Wert erforderlich, die von Benutzern mit großem Aufwand erstellt wurden. In den folgenden Beispielen werden durch die Beibehaltung des Zustands Zeit- oder Hilfsmitteleinsparungen in kommerziellen Aktivitäten erzielt:

  • Webformulare mit mehreren Schritten: Es ist für den Benutzer zeitaufwändig, Daten für mehrere abgeschlossene Schritte eines mehrstufigen Webformulars wiederholt einzugeben, wenn der Zustand nicht mehr vorhanden ist. Der Benutzer verliert in diesem Szenario seinen Zustand, wenn er vom Formular weg navigiert und später zurückkehrt.
  • Warenkörbe: Kommerziell wichtige Komponenten einer App, die potenzielle Umsätze ermöglichen, können beibehalten werden. Ein Benutzer, der seinen Zustand verliert und dessen Warenkorb dadurch gelöscht wird, kann weniger Produkte oder Dienste kaufen, wenn er zu einem späteren Zeitpunkt zur Website zurückkehrt.

Eine App kann nur den App-Zustand beibehalten. Benutzeroberflächen wie Komponenteninstanzen und deren Renderingstrukturen können nicht beibehalten werden. Komponenten und Renderingstrukturen sind in der Regel nicht serialisierbar. Um den Benutzeroberflächenzustand (z. B. von den erweiterten Knoten einer Strukturansicht) zu erhalten, muss die App über benutzerdefinierten Code verfügen, um das Verhalten des Benutzeroberflächenzustands als serialisierbaren App-Zustand modellieren zu können.

Speicherort des Zustands

Es gibt allgemeine Speicherorte für die Beibehaltung des Zustands:

Serverseitige Speicherung

Für permanente Datenspeicherung, die mehrere Benutzer und Geräte umfasst, kann die App unabhängigen serverseitigen Speicher verwenden, auf den über eine Web-API zugegriffen wird. Folgende Optionen sind verfügbar:

  • Blob-Speicher
  • Schlüssel-Wert-Speicher
  • Relationale Datenbank
  • Table Storage

Nachdem die Daten gespeichert wurden, wird der Zustand des Benutzers beibehalten und ist in jeder neuen Browsersitzung verfügbar.

Da Blazor WebAssembly-Apps vollständig im Browser des Benutzers ausgeführt werden, benötigen sie zusätzliche Maßnahmen für den Zugriff auf sichere externe Systeme, z. B. auf Speicherdienste und Datenbanken. Blazor WebAssembly-Apps werden auf die gleiche Weise geschützt wie Single-Page-Anwendungen (SPAs). In der Regel authentifiziert eine App einen Benutzer über OAuth/OpenID Connect (OIDC) und interagiert dann über Web-API-Aufrufe einer serverseitigen App mit Speicherdiensten und Datenbanken. Die serverseitige Anwendung vermittelt die Übertragung von Daten zwischen der Blazor WebAssembly-Anwendung und dem Speicherdienst oder der Datenbank. Die Blazor WebAssembly-App unterhält eine kurzlebige Verbindung mit der serverseitigen App, während die serverseitige App über eine permanente Verbindung mit dem Speicher verfügt.

Weitere Informationen finden Sie in den folgenden Ressourcen:

Weitere Informationen zu den Azure-Datenspeicheroptionen finden Sie hier:

URL

Modellieren Sie vorübergehende Daten, die den Navigationszustand darstellen, als Teil der URL. Beispiele für in der URL modellierte Benutzerzustände:

  • Die ID einer angezeigten Entität.
  • Die aktuelle Seitenzahl in einem ausgelagerten Raster.

Der Inhalt der Adressleiste des Browsers wird beibehalten, wenn der Benutzer die Seite manuell erneut lädt.

Informationen zur Definition von URL-Mustern mit der Anweisung @page finden Sie unter ASP.NET Core: Routing und Navigation in Blazor.

Browserspeicherung

Für vorübergehende Daten, die der Benutzer aktiv erstellt, sind die localStorage- und sessionStorage-Sammlungen des Browsers ein häufig verwendeter Speicherort:

  • localStorage bezieht sich auf das Fenster des Browsers. Wenn der Benutzer die Seite neu lädt oder den Browser schließt und erneut öffnet, wird der Zustand beibehalten. Wenn der Benutzer mehrere Browserregisterkarten öffnet, wird der Zustand auf allen Registerkarten freigegeben. Daten bleiben in localStorage so lange beibehalten, bis sie explizit gelöscht werden.
  • sessionStorage bezieht sich auf die Registerkarte des Browsers. Wenn der Benutzer die Registerkarte noch mal lädt, wird der Zustand beibehalten. Wenn der Benutzer die Registerkarte oder den Browser schließt, geht der Zustand verloren. Wenn der Benutzer mehrere Browserregisterkarten öffnet, hat jede Registerkarte eine eigene unabhängige Version der Daten.

Hinweis

localStorage und sessionStorage können in Blazor WebAssembly-Apps verwendet werden, jedoch nur durch Schreiben von benutzerdefiniertem Code oder Verwenden eines Drittanbieterpakets.

Im Allgemeinen ist sessionStorage in der Anwendung sicherer. sessionStorage verhindert, dass ein Benutzer mehrere Registerkarten öffnet und auf folgende Probleme stößt:

  • Fehler in Zustandsspeicher über mehrere Registerkarten hinweg
  • Verwirrendes Verhalten, wenn eine Registerkarte den Zustand anderer Registerkarten überschreibt

localStorage ist die bessere Wahl, wenn der Zustand der App auch nach dem Schließen und erneuten Öffnen des Browsers bestehen bleiben muss.

Warnung

Benutzer können die Daten anzeigen oder bearbeiten, die in localStorage und sessionStorage gespeichert sind.

Arbeitsspeicherinterner Zustandscontainer des Diensts

Geschachtelte Komponenten binden Daten in der Regel mithilfe verketteter Bindungen, wie in Blazor-Datenbindung in ASP.NET Core beschrieben. Geschachtelte und nicht geschachtelte Komponenten können den Zugriff auf Daten über einen registrierten arbeitsspeicherinternen Zustandscontainer teilen. Eine benutzerdefinierte Zustandscontainerklasse kann eine Action zuweisen, um Komponenten in verschiedenen Teilen der App über Zustandsänderungen zu informieren. Im folgenden Beispiel:

  • Ein Komponentenpaar verwendet einen Zustandscontainer, um eine Eigenschaft nachzuverfolgen.
  • Eine Komponente im folgenden Beispiel ist in der anderen Komponente geschachtelt, aber Schachtelung ist nicht erforderlich, damit dieser Ansatz funktioniert.

Wichtig

Das Beispiel in diesem Abschnitt veranschaulicht, wie Sie einen In-Memory-Containerdienst erstellen, den Dienst registrieren und den Dienst in Komponenten verwenden. Das Beispiel speichert keine Daten ohne weitere Entwicklung. Für die dauerhafte Speicherung von Daten muss der Statuscontainer einen zugrunde liegenden Speichermechanismus übernehmen, der weiterhin besteht, wenn der Browserspeicher gelöscht wird. Dies kann mit localStorage/sessionStorage oder einer anderen Technologie erreicht werden.

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

Clientseitige Apps (Program Datei):

builder.Services.AddSingleton<StateContainer>();

Serverseitige Apps (Program Datei, ASP.NET Core in .NET 6 oder höher):

builder.Services.AddScoped<StateContainer>();

Serverseitige Apps (Startup.ConfigureServices von Startup.cs, ASP.NET Core vor 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;
    }
}

Die vorangehenden Komponenten implementieren IDisposable, und die OnChange-Delegaten werden in den Dispose-Methoden abbestellt, die vom Framework aufgerufen werden, wenn die Komponenten verworfen werden. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Weitere Ansätze

Bei der Implementierung des benutzerdefinierten Zustandsspeichers empfiehlt es sich, kaskadierende Werte und Parameter zu übernehmen:

  • Um den Zustand über viele Komponenten hinweg zu nutzen.
  • Es muss nur ein Zustandsobjekt auf der obersten Ebene beibehalten werden.

Problembehandlung

In einem benutzerdefinierten Zustandsverwaltungsdienst muss ein Rückruf, der außerhalb des Synchronisierungskontexts von Blazor aufgerufen wird, die Logik des Rückrufs in ComponentBase.InvokeAsync verpacken, um ihn in den Synchronisierungskontext des Renderers zu verschieben.

Wenn der Zustandsverwaltungsdienst StateHasChanged nicht über den Synchronisierungskontext von Blazor aufruft, wird der folgende Fehler ausgelöst:

System.InvalidOperationException: „Der aktuelle Thread ist nicht dem Dispatcher zugeordnet. Verwenden Sie InvokeAsync() zum Wechseln der Ausführung auf den Dispatcher beim Auslösen des Renderings oder des Komponentenzustands.“

Weitere Informationen und ein Beispiel für die Behebung dieses Fehlers finden Sie unter ASP.NET Core Razor Komponentenrendering.

Zusätzliche Ressourcen