Blazor WebAssembly, Teil 4: Zustandsverwaltung und Nachladen von Modulen

Seite 2: Blazored.LocalStorage

Inhaltsverzeichnis

Das Paket Blazored.LocalStorage ist zum Zeitpunkt der Erstellung dieses Beitrags aktueller gewartet, hat mehr GitHub-Sterne und deutlich mehr NuGet-Downloads (330.700) als Blazor.Extensions.Storage (36.100). Daher fällt die Wahl auf ersteres. Zunächst ist das Paket Blazored.LocalStorage per grafischer NuGet-Benutzeroberfläche in Visual Studio oder in der NuGet Package Manager Console zu einem Blazor-Projekt hinzuzufügen:

Install-Package Blazored.LocalStorage

Abhängig von der verwendeten Blazor-Version müssen die Leser gegebenenfalls eine bestimmte, kompatible Version des Pakets installieren. Da sich das Paket unabhängig von Blazor versioniert, lässt sich hier nicht vorhersagen, welche Version das beim Erscheinen dieses Beitrags sein wird.

Dann ist der Local Storage Service der Komponente im Dependency-Injection-Container in Program.cs zu registrieren:

using Blazored.LocalStorage;
...
services.AddBlazoredLocalStorage();

Nun können sich Softwareentwickler in jeder Razor Component oder anderen Klassen eine Klasse injizieren lassen, die Blazored.LocalStorage.ILocalStorageService bereitstellt. Diese Schnittstelle bietet einige Methoden für das Speichern von Name-Wert-Paaren an:

  • SetItemAsync(): Setzen eines Werts im Local Storage unter Angabe von Namen und Wert
  • GetItemAsync(): Laden eines Werts anhand des Namens
  • RemoveItemAsync(): Entfernen eines Werts anhand des Namens
  • ClearAsync(): Entfernen aller Werte
  • LengthAsync(): Liefert die Anzahl der gespeicherten Werte
  • KeyAsync(): liefert einen Wert per numerischem Index
  • ContainsKeyAsync(): prüft, ob zu einem Namen einen Wert gibt

In diesem Fall wird eine Zeichenkette (das o. g. Token) persistiert; es lassen sich aber auch komplexe Objekte übergeben, sofern diese in JSON serialisierbar sind, denn der Zugriff auf die Web Storage API erfolgt über JavaScript.

Die Injizierung einer Klasse für die Schnittstelle Blazored.LocalStorage.ILocalStorageService wird in AuthenticationManager.cs benötigt:

public class AuthenticationManager : AuthenticationStateProvider 
 {
  MiracleListAPI.MiracleListProxy proxy { get; set; }
  Blazored.LocalStorage.ILocalStorageService storage { get; set; }

  public AuthenticationManager(MiracleListAPI.MiracleListProxy proxy, Blazored.LocalStorage.ILocalStorageService storage )
  {
   this.proxy = proxy;
   this.storage = storage;
  }
...
}

In der gleichen Klasse ergänzt man nun eine Methode CheckLocalTokenValid(), die mit GetItemAsync() versucht, ein Token aus dem Local Storage des Webbrowsers zu laden. Falls ein Token vorhanden ist, wird es durch einen Aufruf der flexiblen WebAPI-Operation /LoginAsync geprüft. Hier können Nutzer statt Benutzernamen und Kennwort auch ein Token übergeben. Wenn das Token noch gültig ist, liefert die WebAPI den Benutzernamen zurück, und durch Aufruf der eigenen Methode Notify() informiert der Code die Blazor-Infrastruktur über die erfolgreiche Benutzeranmeldung. Abbildung 1 zeigt in der Browserkonsole die Ausgaben des Authentication Manager beim Aufruf von CheckLocalTokenValid() bei vorhandenem Token im Local Storage.

Das noch gültige Token im Local Storage sorgt dafür, dass Benutzer sich nicht nochmals neu anmelden müssen (Abb. 1).
public async Task<bool> CheckLocalTokenValid()
  {
   bool result = false;
   string token = await localStorage.GetItemAsync<string>(STORAGE_KEY);
   if (!String.IsNullOrEmpty(token))
   {
    // Es gibt ein Token im Local Storage. Nachfrage beim Server, ob noch gültig.
    Console.WriteLine($"{nameof(AuthenticationManager)}.{nameof(CheckLocalTokenValid)}: Checking local token {token}...");
    var l = new LoginInfo() {Token=token, ClientID = AuthenticationManager.ClientID };
    this.CurrentLoginInfo = await proxy.LoginAsync(l);
    if (this.CurrentLoginInfo == null || String.IsNullOrEmpty(CurrentLoginInfo.Token))
    { // Token ungültig!
     Console.WriteLine($"{nameof(AuthenticationManager)}.{nameof(CheckLocalTokenValid)}: Token not valid: {CurrentLoginInfo?.Message}!");
     CurrentLoginInfo = null;
    }
    else
    { // Token gültig!
     Console.WriteLine($"{nameof(AuthenticationManager)}.{nameof(CheckLocalTokenValid)}: Found valid Token: {CurrentLoginInfo.Token} for User: {CurrentLoginInfo.Username}");
     Notify();
     result = true;
    }
   }
   else
   {
    Console.WriteLine($"{nameof(AuthenticationManager)}.{nameof(CheckLocalTokenValid)}: No local token!");
   }
   Notify();
   return result;
  }

Listing 1: Erweiterung der Klasse AuthenticationManager.cs

Diese neue Methode CheckLocalTokenValid() aus Listing 1 lässt sich dann in der Razor Component "Login" in der Code-Behind-Datei aufrufen (s. Listing 2). Die Prüfung, ob ein Token vorhanden ist, sollte in der Lebenszyklusmethode OnInitializedAsync() erfolgen; hier kann man dann vor dem Rendern von Inhalten Benutzer mit dem NavigationManager schon auf die Hauptansicht leiten. Allerdings muss die Prüfung des Tokens im Programmcode erst nach der Abfrage folgen, ob die Benutzer eine Abmeldung wünschen; sonst würde diese niemals erreicht werden.

  protected override async System.Threading.Tasks.Task OnInitializedAsync()
  {
   // Reaktion auf die URL /logout
   if (this.NavigationManager.Uri.ToLower().Contains("/logout"))
   {
    await ((AuthenticationManager)asp).Logout(); return;
   }

   // Direkt zur Hauptseite, falls ein Token im Local Storage ist
   if (await (asp as AuthenticationManager).CheckLocalTokenValid())
   {
    this.NavigationManager.NavigateTo("/main");
   }
  }

Listing 2: Erweiterung von Login.razor.cs

Eine direkte Interaktion mit dem Local Storage ist noch an zwei weiteren Stellen in der Klasse AuthenticationManager erforderlich:

  • In der Methode Login() ist bei erfolgreicher Anmeldung das Token im Local Storage zu setzen: await localStorage.SetItemAsync<string>(STORAGE_KEY, this.CurrentLoginInfo.Token);
  • In der Methode Logout() muss man das Token mit await localStorage.RemoveItemAsync(STORAGE_KEY) entfernen, sonst würden Benutzer immer wieder neu angemeldet werden.

Nun sollte die Anmeldung bei der Webanwendung rundlaufen:

  • Wenn Benutzer sich erstmals anmelden, werden sie in der Methode AuthenticationManager.Login() angemeldet, ihr Token lokal persistiert und dann von Login.razor.cs mit /Main auf die Seite Index.razor gelenkt.
  • Wenn Benutzer den Browser schließen und die Webanwendung neu öffnen, prüft CheckLocalTokenValid() das persistierte Token erneut gegen das Backend und leitet die Benutzer dann auf /Main.
  • Sofern Benutzer direkt /Main anspringen, gibt es keine Benutzerprüfung. Sie werden aber – wie im Teil 3 implementiert – auf / geleitet und landen damit bei einem der ersten beiden Fälle.

Wenn Benutzer auf Logout klicken, wird per <a>-Tag die relative URL /Logout gerufen und die Komponente Login.razor reagiert darauf mit Abmeldung beim Backend und Löschen des Tokens im Local Storage.