Blazor WebAssembly, Teil 4: Zustandsverwaltung und Nachladen von Modulen
Seite 2: Blazored.LocalStorage
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 WertGetItemAsync()
: Laden eines Werts anhand des NamensRemoveItemAsync()
: Entfernen eines Werts anhand des NamensClearAsync()
: Entfernen aller WerteLengthAsync()
: Liefert die Anzahl der gespeicherten WerteKeyAsync()
: liefert einen Wert per numerischem IndexContainsKeyAsync()
: 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.
Erweiterung der Klasse AuthenticationManager
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.
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 mitawait 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.