© Complot/shutterstock.com
State Management in Blazor WebAssembly und Blazor Server

Blazor erhält Zustände


Razor Components, die Komponenten in Microsofts neuem Web-Framework Blazor, behalten ihre Zustände, solange nicht das Browserfenster geschlossen wird bzw. bei Blazor Server die Verbindung zum Server verloren geht. Dieser Beitrag behandelt sowohl die eingebaute Zustandsverwaltung als auch Lösungen für langlebige Zustände mit NuGet-Erweiterungen und dem Einsatz von JavaScript-Interoperabilität.

Klassische Webanwendungen mit Server-side Rendering sind zustandslos: Ein HTTP Request geht auf dem Webserver ein, wird verarbeitet, und irgendwann wird eine Antwort zum Browser gesendet. Nach Absenden der Antwort werden alle auf dem Webserver gesetzten Variablen vernichtet. Konkret wird in ASP.NET und ASP.NET Core bei jedem Aufruf immer wieder eine neue Instanz der Page-Klasse (bei Web Forms), der Controller-Klasse (bei MVC) bzw. des Page Models (bei Razor Pages) erzeugt. Man braucht Techniken wie Cookies, Hidden Fields oder URL-Parameter bzw. darauf aufsetzende Abstraktionen wie Session-Variablen oder ViewState, um Werte von einem zum nächsten HTTP-Aufruf weiterzugeben.

In vielen Fällen ist keiner dieser Workarounds notwendig – weder in Blazor Server noch in Blazor WebAssembly, denn Blazor ist ein zustandsbehaftetes Programmiermodell. Hier gibt es einen sogenannten Circuit (auf Deutsch: Schaltkreis, Stromkreis, Bezirk), der Zustände der Komponenten und injizierter Klasseninstanzen speichert. Eine weitergehende Zustandsverwaltung unter Verwendung des Browserspeichers ist geboten, wenn man über die Lebensdauer des Circuits hinaus Zustände erhalten will, z. B. nach einem Neuladen oder Neuöffnen des Webbrowsers.

Zwischen Blazor Server und Blazor WebAssembly gibt es aber auch hier wieder den Unterschied, dass bei Blazor WebAssembly die Komponentenzustände im Hauptspeicher des Webbrowsers und bei Blazor Server im Hauptspeicher des Webservers liegen.

Lebenszeit des Komponentenzustands

Eine Instanz einer Razor Component (und alle in ihr per Field oder Property gespeicherten Werte sowie das zugehörige Virtual DOM) lebt, solange sie zur aktuellen Ansicht gehört. Eine Komponenteninstanz gehört zur aktuellen Ansicht, wenn die Komponente

  1. entweder über eine Route (einen relativen URL) direkt aktiviert ist

  2. in eine andere Komponente eingebettet ist, die über eine Route aktiviert ist

  3. in eine andere Komponente eingebettet ist, für die Satz 2 gilt

Testanwendung: Counter

Dass Razor Components zustandsbehaftet sind, zeigt schon das Zählerbeispiel (Counter.razor), das Microsoft in der Standardprojektvorlage mitliefert (Listing 1). Wegen der Zustandshaftigkeit von Blazor wird der Counter bei jedem Klick erhöht. In klassischen Webanwendungen würde er immer nur von 0 bis 1 hochzählen.

Listing 1: Counter-Beispiel: Blazor merkt sich Zustände der Komponenten, solange sie sichtbar sind

@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { int currentCount = 0; void IncrementCount() { currentCount++; } }

Verlust des Zustands

Aber auch in Blazor lebt der Zustand nicht ewig. Der komplette Zustand einer Komponenteninstanz geht unter folgenden Umständen verloren:

  • Wenn eine Komponenteninstanz nicht mehr zur aktuellen Ansicht gehört (z. B. durch Navigation)

  • Wenn der Benutzer einen Reload im Browser ausführt oder den Browser neu startet.

  • Bei Blazor Server: bei Verlust des Kontakts zwischen Webbrowser und Webserver für mehrere Sekunden; dann löscht Blazor Server den Circuit mit allen Komponentenzuständen

Eine Komponente muss keinen Inhalt rendern und kann dennoch weiterleben. Sie kann auch zeitweilig oder dauerhaft nichts anzeigen. Keinen Inhalt zu rendern führt also nicht zum Verlust des Zustands. Die Testanwendung in Abbildung 1 und Listing 2 demonstriert eindrucksvoll die Umstände, unter denen der Zustand verloren bzw. nicht verloren geht. Die Testanwendung besteht aus zwei eingebetteten <Counter>-Komponenten und jeweils einem Auswahlfeld, in dem der Benutzer folgende Optionen hat:

  • render + visible

  • do not render

  • render + invisible

schwichtenberg_razor_components_1.tif_fmt1.jpgAbb. 1: Oberfläche der State-Management-Testanwendung für Blazor

In Listing 2 sieht man, was die Einstellungen bedeuten:

  • render + visible: Die <Counter>-Komponente wird gerendert und für den Benutzer angezeigt, der umgebende <div>-Block steht auf display:block.

  • do not render: Durch eine if-Bedingung wird das Rendern der <Counter>-Komponente verhindert.

  • render + invisible: Die <Counter>-Komponente wird gerendert, aber der umgebende <div>-Block steht auf display:none, sodass der Benutzer nichts von der aktiven Komponente sieht.

Listing 2: Quellcode für die State-Management-Testanwendung

@page "/Demos/StateManagement" <h3>Blazor State Management Demo </h3> <h4>(C) Dr. Holger Schwichtenberg 2019</h4> @code { string Counter1Visibility { get; set; } = "render + visible"; string Counter2Visibility { get; set; } = "render + visible"; } @*Counter 1*@ <hr /> Settings for Counter 1: <select @bind="Counter1Visibility"> <option>render + visible</option> <option>do not render</option> <option>render + invisible</option> </select> @if (Counter1Visibility != "do not render") { <div style="@((Counter1Visibility != "render + invisible") ? "display:block" : "display:none")"> <Counter> </Counter> </div> } @*Counter 2*@ <hr /> Settings for Counter 2: <select @bind="Counter2Visibility"> <option>render + visible</option> <option>do not render</option> <option>render + invisible</option> </select> @if (Counter2Visibility != "do not render") { <div style="@((Counter2Visibility != "render + invisible") ? "display:block" : "display:none")"> <Counter> </Counter> </div> }

Nun ist folgendes Verhalten zu beobachten:

  • Die beiden Zähler zählen in jedem Fall unabhängig voneinander, d. h. jede Instanz besitzt ihren eigenen Zustand.

  • Wenn man einen Zähler, der schon einen Wert größer 0 hat, auf do not render stellt und dann wieder zurück auf eine andere Option wechselt, beginnt der Zähler wieder bei 0. Das bedeutet, er hat einen Zustand verloren, weil er nicht mehr zur aktuellen Ansicht gehörte.

  • Wenn man einen Zähler, der schon einen Wert größer 0 hat, auf render + invisible und dann zurück auf render + visible stellt, macht der Zähler beim letzten Wert weiter. Die Komponente wurde weiterhin gerendert, es gab aber keinen sichtbaren Inhalt.

Bewahrung des Zustands

Für die Bewahrung von Werten auch über den Verlust des Komponentenzustands hinaus zeigt Tabelle 1 mehrere Alternativen, die sowohl in Blazor WebAssembly als auch Blazor Server funktionieren. In diesem Beitrag werden die ersten drei Optionen diskutiert. Die Persistierung in der Datenbank ist ein umfangreicheres Thema, das hier keinen Raum findet.

Strategie

Nicht mehr Teil der aktuellen Ansicht

Reload und Browserneustart

Verbindungsverlust (nur bei Blazor Server)

Sitzungszustand innerhalb von Blazor

hilft

hilft nicht

hilft nicht

Session Storage des Browsers bzw. Session Cookie...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang