Weiter geht es mit Teil 3 der Blogbeitragsreihe für Blazor WASM Prerender mit ASP.NET Core als Host in .NET 8.
In diesem Blogbeitrag erfährst du, wie du optimal für deinen Anwendungsfall Prerendering mit Blazor 8 verwendest. Dabei ist es keine «one-fits-all» Lösung.
Wenn du dich fragst, was Prerendering überhaupt ist, hüpf doch rüber zum ersten Teil der Beitragsreihe Prerender mit Blazor WebAssembly (WASM) in ASP.NET Core Teil 1. Wenn du Prerendering vor .NET 8 verwendest und trotzdem alle Vorteile nutzen willst, hilft dir der zweite Teil Prerender mit Blazor WebAssembly (WASM) in ASP.NET Core Teil 2 Deep Dive weiter.
Bevor es losgeht ein Überblick über die Themen, die dich in diesem Blogbeitrag erwarten:
- Cascading Authentication State
- RedirectToLogin während Prerendering
- Prerendering für bestimmte Seiten
Ich arbeite in diesem Beispiel mit dem Blazor Web App Template von Microsoft. Hier siehst du die Konfigurationen:
Zum Aspekt mit dem Prerendering von Daten hat sich nicht viel geändert. Das und alle Punkte dieses Blogbeitrags, findest du im Projekt auf unserem GitHub: https://github.com/databinding-gmbh/Blazor8_Prerender_Blogbeitrag_Teil3
Eine Überischt der neuen Render Modes findest du hier: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0#render-modes.
Umbauen auf .NET 8
Falls du eine .NET 7 oder ältere Anwendung hast, kannst du diese mit der Micorsoft Anleitung umbauen: https://learn.microsoft.com/en-us/aspnet/core/migration/70-80?view=aspnetcore-8.0&tabs=visual-studio#convert-a-hosted-blazor-webassembly-app-into-a-blazor-web-app
Hier ist die Microsoft Dokumentation für Blazor 8 Prerendering: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0#prerendering
Cascading Authentication State
Als erstes möchte ich dir einen kleinen, aber einflussreichen Unterschied zwischen…
builder.Services.AddCascadingAuthenticationState();
…und…
<CascadingAuthenticationState> <Router AppAssembly="typeof(Program).Assembly"> … </Router> </CascadingAuthenticationState>
…zeigen.
Laut Microsoft gibt es keinen Unterschied. Das stimmt so aber nicht abschliessend.
Um es dir aufzuzeigen, gehe in die Server Klasse PersistingServerAuthenticationStateProvider.cs und füge folgenden Codeabschnitt hinzu:
public override Task<AuthenticationState> GetAuthenticationStateAsync() { return base.GetAuthenticationStateAsync(); }
Setze einen Breakpoint in die Methode und starte das Projekt. Du wirst sehen, dass der Breakpoint nie ausgelöst wird.
Warum? Da bin ich nach langer Suche, ebenfalls nicht schlau geworden. Falls du die Erklärung dazu hast, lass es mich über ein Kommentar wissen.
Wechsle nun in die Program.cs vom Server und Client Projekt. Kommentiere builder.Services.AddCascadingAuthenticationState();
aus. Im Routes.razor solltest du stattdessen, wie oben gezeigt, <CascadingAuthenticationState>
einfügen.
Starte das Projekt neu. Und siehe da, der Breakpoint wird ausgelöst:
Dieses Beispiel zeigt, obschon ich angemeldet bin, leitet es mich auf die Login-Seite um. Dies weil der PersistingServerAuthenticationStateProvider für Testzwecke einen nicht authentifizierten Benutzer zurückgibt, wenn die Route /auth aufgerufen wird.
Server Authentication State Provider
Mit dem Server Authentication State Provider kann während des Prerendering der Authentication State angepasst werden. Wichtig dabei ist der vorhin erwähnte Unterschied, wie die Komponente eingefügt wird.
Ohne diese Änderungen wird die Methode nie aufgerufen und zusätzliche Logik, welche den Authentifizierungsstatus des Benutzers anpassen, übersprungen.
Je nach Anforderungen kann diese Methode essenziell sein.
Diese Logik auf den Client zu verlagern, ist nicht empfehlenswert, da es zu Manipulationen führen kann. Stichwort BFF: https://medium.com/mobilepeople/backend-for-frontend-pattern-why-you-need-to-know-it-46f94ce420b0.
RedirectToLogin während Prerendering
Im Internet habe ich viel Verwirrung und Unklarheiten über die im Template vorhandene `RedirectToLogin.razor` Komponente gesehen. Die grösste Diskussion dabei auf GitHub: https://github.com/dotnet/aspnetcore/issues/52063
Jemand sagte, dass die RedirectToLogin.razor niemals ausgelöst wird und somit überflüssig sei. Dies stimmt aber nur bedingt. Wenn wir einen Breakpoint in die OnInitialized() Methode der Komponente setzen, sehen wir, dass die Komponente aufgerufen wird. Also wenn wir versuchen /auth aufzurufen.
Jetzt kommt aber der Haken an der Sache. Wird die Seite initial, also während des Prerenderings, geladen, wird RedirectToLogin tatsächlich nicht aufgerufen.
Während des Prerendering Requests wird dich nicht Blazor umleiten, sondern Asp.Net Core bzw. Asp.Net Identity.
Um das Problem zu umgehen, kann folgendes in Program.cs vom Server angehängt werden:
app.MapRazorComponents<App>() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(test.Client._Imports).Assembly) .AllowAnonymous();
Und nein, die /auth Seite kann so nicht anonym aufgerufen werden. Es erlaubt nur, dass App.razor die Routes.razor Komponente Prerendern kann.
verhindert während des Prerendern dann das Anonyme aufrufen:<AuthorizeRouteView>
<NotAuthorized> <RedirectToLogin /> </NotAuthorized>
Resultat:
Das gleiche gilt ebenfalls für den Server und Auto RenderMode wenn Prerendering aktiviert ist.
Jemand hat folgende Lösung vorgeschlagen;
..AddCookie(IdentityConstants.ApplicationScheme, opt => opt.LoginPath = "routeUrl")
Dies ist aber nicht empfohlen, da dies nicht die Konfigurationen für Blazor sind, sondern für die API/MVC Endpunkte.
Prerendering für bestimmte Seiten / Komponenten
Das Prerendering für bestimmte Seiten kann nun direkt auf der Seite eingestellt werden. Wähle dafür Interactivity location Per page/component aus.
Setzte auf den gewünschten Seiten, die kein Prerendering haben sollen folgendes hinzu:
@page "/weather" @rendermode @(new InteractiveWebAssemblyRenderMode(false))
Ist Interactivity location auf Per page/component gesetzt, landet die Routes.razor im Server Projekt:
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"> <Found Context="routeData"> <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> </AuthorizeRouteView> <FocusOnNavigate RouteData="routeData" Selector="h1" /> </Found> </Router>
Wie ich feststellen konnte, führt dies dazu, dass jede Seite (egal ob im Server oder Client Projekt plaziert) immer vom Server geladen wird:
Dies scheint mir seltsam, da es den Zweck der SPA (Single Page Application) vernachlässigt.
Dies ist ein Thema für einen weitern Blogbeitrag, da ich mich hier auf die Prerenderfunktionen konzentriere. Lasse es mich gerne wissen, wenn du den genauen Grund bzw. die Absicht für dieses Verhalten kennst.
Fazit
Einiges hat sich geändert mit Blazor 8. Einige Punkte sind noch nicht ausgereift. Dies sieht man an der Redirect Komponente und fehlende Dokumentationen für bestimmte Verhaltensweisen von Blazor.
Ein weiteres Beispiel lässt sich anhand <NotFound> im Router zeigen. Seit .NET 8 funktioniert dies nicht mehr wie bisher. Dazu habe ich auch ein GitHub Ticket gefunden: https://github.com/dotnet/aspnetcore/issues/52660.
Ich muss aber sagen, dass ich kein Fan dieser Lösung bin. Weder von dieser noch von der vorherigen genannten die das RedirectToLogin «löst». Asp.Net Core (Host) und Blazor (WASM, Server, Auto, SSR) Logik sollten nicht vermischt werden. Der Host sollte nur zuständig sein, die Blazor App zurückzugeben und Daten über den Endpunkt bereitzustellen. Wird diese Logik vermischt mit Lösungen wie:
app.UseStatusCodePagesWithRedirects("/404");
…oder…
..AddCookie(IdentityConstants.ApplicationScheme, opt => opt.LoginPath = "routeUrl")
…führt dies nur zu weiteren Problemen. Vorallem dann, wenn der Host API- oder MVC-Endpunkte anders behandeln soll als Blazor Seiten.
Trotzdem finde ich Blazor 8 einen weiteren Schritt in die richtige Richtung. Ich bin gespannt wie die Schwierigkeiten mit Blazor behoben werden und welche neue Funktionen mit .NET 9 auf uns zukommen. Die .NET 9 Preview 5 wurde kürtzlich veröffentlicht. Es verspricht eine einfachere Abfrage des aktuellen Render Mode. Zudem auch eingebaute Funktionen für das übertragen des Authentication State an die WASM Anwendung: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview5/aspnetcore.md
Schlusswort
Was hast du für Erfahrungen mit Blazor 8 gemacht? Welche Lösungen haben für dich funktioniert und welche nicht?
Lass es mich mit einem Kommentar wissen.
Was dich als nächstes für Blogbeiträge mit Thema Blazor erwartet:
- API Endpunkt die Umleiten (Found 302)
- Splash Screen ohne Prerendering oder JavaScript
Sei ausserdem auf einen zeitnahen Beitrag von Leonie, über ihre Lernreise durch den Advanced Product Owner Kurs, gespannt.