.NETversum
.NETversum
Blazor Server kennt drei sogenannte Render Modes. Das legt der Entwickler in der _Host.cshtml-Datei im Attribut render-mode im Tag <component> fest:
ServerPrerendered
Server
Static
Der Standard laut Microsoft-Projektvorlage ist "Server-Prerendered". Es muss immer ein Render Mode angegeben sein, sonst gibt es die Laufzeitfehlermeldung „A value for the 'render-mode' attribute must be supplied to the 'component' tag helper. (Parameter 'RenderMode')“.
Der Tag <component> ist ein sogenannter Tag Helper. Tag Helper gibt es in Views in ASP.NET Core MVC (.cshtml) und ASP.NET Core Razor Pages (auch .cshtml). Alternativ kann man den Tag Helper auch per Code ausdrücken. So kann man statt
<component type="typeof(App)" render-mode="ServerPrerendered" />
auch die Methode RenderComponentAsync() aufrufen:
@(await @Html.RenderComponentAsync(typeof(App), RenderMode.Static, null))
Der Render Mode "ServerPrerendered" bedeutet, dass schon beim Abruf der Webanwendung das Tag
<component type="typeof(App)" render-mode="ServerPrerendered" />
ersetzt wird durch den kompletten HTML-Inhalt aller beim Start der Anwendungen sichtbaren Komponenten (hier „Startkomponenten“ genannt). Der Inhalt wurde auf dem Server vorgeneriert und in der HTTP-Antwort ausgeliefert. Der Client bekommt also den Code aus Listing 1. Der Inhalt ist zunächst statisch. Vollautomatisch werden dann sogleich die Startkomponenten erneut zum Rendern aufgefordert, dieses Mal mit interaktiven Inhalten.
Listing 1
<!--Blazor:{"sequence":0,"type":"server","prerenderId":"afcb4cd8b81642d288f092f37177db03","descriptor":"CfDJ8BhdxqNUIqRKuoxeSE/bsvk1TssD18yYaNmxCg3dCE3H9\u002BYCKDf4FI\u002BHmb/fBptZQICbcG6PP8hONtwVxPA7OiDsNp6o\u002Bc0\u002Bo2ZKe5oUPFTbrLYNIldlC4FbuTUZ88oEIuagp4Z\u002BgtilXomR989\u002BcNUZToNq7ULRWPdKRxXEMhVqgU5vJU3nAkMuOZ1sZQ6c3gUidXKcClyFY/6XMM4PCxomT5RcaS8tz\u002BMWxkt1Tx7Q4tDLdP2oQHbqRSG9nf6Qd4HUnaXg20x2lfWX8TH0t8aUIeTE2UtnQ9h51HTeAuLtG3OBHX1drx26wIq3A/t//ULvUHxoTscTNEU8qpuNHQPiCdo6E\u002BonJUegRXOn2Mnk"}-->
<div> Text – nur ein Beispiel für den kompletten Inhalt der jeweiligen Startkomponente!</div>
Der Vorteil dieses Verfahrens ist, dass der Browserbenutzer schnell die ersten Inhalte sieht. Der Nachteil dieses Verfahrens ist, dass die Methoden OnInitialized() und OnInitializedAsync() aller Startkomponenten jeweils zweimal(!) aufgerufen werden: einmal beim Pre-Rendering und dann zu Beginn der eigentlichen Komponentenverarbeitung. Beim ersten Aufruf im Rahmen des Pre-Rendering ist dabei keine JavaScript-Interoperabilität verfügbar. Ein Versuch, JavaScript aufzurufen, führt zum Laufzeitfehler. Diese Einschränkungen betreffen wie gesagt nur die Startkomponenten, nicht weitere Komponenten.
Der Render Mode "Server" bedeutet, dass beim Abruf der Webanwendung der Tag
<component type="typeof(App)" render-mode="Server" />
nur durch einen „Marker“ ersetzt wird. Der eigentliche Inhalt wird dann erst danach häppchenweise per WebSocket-Übertragung geliefert. Der Client bekommt also zuerst nur den Code aus Listing 2.
Listing 2
<!--Blazor:{"sequence":0,"type":"server","descriptor":"CfDJ8BhdxqNUIqRKuoxeSE/bsvngtp4ndTGp9/4qikYGof\u002BFFAgQCfQqqsEiQTkzeZ\u002BpiV22xSDqiq\u002BPME/M2L\u002BlbAc0HPXTAP6xVpIH\u002BNoj5ZPsopgGVvTM\u002BK59MRulNau0u/RxXfza4oRqw5jz92Mh2ixrcrXPIj4UHn0Aa/Vjjnutpi/zF\u002Bs3LvJQ6Q87ZJzoBiOOF\u002BAuKLwZcqtWxIsdSQERYNPvEyDy0K9Ea/J3rSijreP/3miwbLKoc9T5sDeX0RP21UEwHXyXExKn2LK8uRlOskSG4HQEpKBReYI0pfmxkxOvd6QT\u002BbHHV0MyGnJcggejIhDiZlTet0ufOtHHipQCJC0daCC6jdnFmyuUZ8Uy"}-->
Der Vorteil dieses Verfahrens ist, dass es nur eine Initialisierungsphase der Startkomponenten gibt und dort auch JavaScript-Interoperabilität möglich ist. Der Nachteil dieses Verfahrens ist, dass der Browserbenutzer länger auf die ersten Inhalte warten muss.
Der Render Mode "Static" bedeutet, dass beim Abruf der Webanwendung der Tag
<component type="typeof(App)" render-mode="Server" />
genau wie bei "ServerPrerendered" bereits durch Inhalte der Startkomponenten ersetzt wird. Es wird kein Marker für die WebSocket-Interaktion eingefügt, d. h., es gibt hier gar keine WebSocket-Verbindung:
<div> Text – nur ein Beispiel für den kompletten Inhalt der jeweiligen Startkomponente!</div>
Der Vorteil dieses Verfahrens ist, dass der Browserbenutzer sehr schnell die ersten Inhalte sieht. Der Nachteil dieses Verfahrens ist, dass alle Komponenten aus der Sicht von Blazor nicht interaktiv sind, d. h., DOM-Ereig
nisse werden nicht an den Server gemeldet. JavaScript ist in der Initialisierungsphase nicht möglich. Der Benutzer kann aber Hyperlinks in der Seite anklicken oder Submit-Schaltflächen drücken, um zu anderen Komponenten zu gelangen, die dann serverseitig wieder statisch gerendert werden.
Nur im Render Mode "static" ist eine Parameterübergabe an die Komponente möglich, wenn die Komponente entsprechende Parameter vorsieht (Listing 3).
Listing 3: Beispiel für eine Komponente mit Parametern
<h3>Komponente2</h3>
Text: @Starttext
<br />
Zahl: @Startwert
<button @onclick="Klick">Klick mich!</button>
@ausgabe
@code
{
[Parameter]
public int Startwert { get; set; }
[Parameter]
public string Starttext { get; set; }
public string ausgabe { get; set; } = "";
private void Klick()
{
ausgabe = DateTime.Now.ToString();
}
}
Diese Komponente kann man in eine ASP.NET MVC View (.cshtml) oder ASP.NET Core Razor Page (.cs-html) einbinden mit
<component type="typeof(Web.Demos.EigenständigeKomponenten.Komponente2)" render-mode="Static" param-Startwert="1" param-Starttext="@("Hallo Welt!")"/>
oder
@(await @Html.RenderComponentAsync(typeof(App), RenderMode.Static, new { Startwert = 123, Starttext = "Hallo Welt!" }))
ASP.NET MVC View (.cshtml) oder ASP.NET Core Razor Page (.cshtml) können mehrere Blazor-Server-Komponenten mit unterschiedlichen Render Modes gleichzeitig beheimaten. Somit ist es möglich, Webanwendungen zu erstellen, die sich aus klassischen Webseiten mit vollständigem Server-Rendering und Webseiten mit Single Page Look and Feel zusammensetzen.
Ein Beispiel zeigt Listing 4, das zwei Komponenten einbindet:
Komponente1 ohne Parameter, hier nicht abgedruckt,
Komponente2 mit Parametern (Listing 3)
Listing 4: Ausschnitt aus einer Razor Page (.cshtml-Datei) mit mehreren integrierten Blazor-Server-Komponenten
@page "/"
@namespace Web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ITVisions.Blazor
@inject BlazorUtil Util
<!DOCTYPE html>
<html>
...
<div class="container-fluid">
@(await @Html.RenderComponentAsync(typeof(App), RenderMode.Static, null))
<component type="typeof(Web.Demos.EigenständigeKomponenten.Komponente1)" render-mode="ServerPrerendered" />
<component type="typeof(Web.Demos.EigenständigeKomponenten.Komponente2)" render-mode="ServerPrerendered" param-Startwert="1" param-Starttext="@("Hallo Welt!")" />
@(await @Html.RenderComponentAsync(typeof(Web.Demos.EigenständigeKomponenten.Komponente2), RenderModeServer-Prerendered, new { Startwert = 123, Starttext ="Hallo Welt!" }))
</div>
...
</html>
Es gibt Situationen, in denen Blazor beim Rendern in Schleifen die Benutzeroberfläche nicht korrekt ändert, wenn sich die Daten ändern. Abbildung 1 und 2 zeigen so ein Beispiel: Es erfolgt ein Kategorienwechsel von „Haushalt“ zu „Freizeit“ in der MiracleList-Anwendung [1], [2], aber die Wahl der Checkbox bleibt unverändert, obwohl „Kino“ noch gar nicht erledigt ist.
Den entsprechenden Code dazu zeigt Listing 5 (in Ausschnitten). Hier muss man der Änderungsverfolgung von Blazor auf die Sprünge helfen, indem man Blazor mit @ key klarmacht, was in der Schleife eindeutig ist, z. B. @ key="t" oder @key="t.ID" (Listing 6).
Listing 5
@foreach (BO.Task t in taskSet)
{
<li @onclick="() => ShowTaskDetail(t)" … title="Task #@t.TaskID">
...
<input type="checkbox" id="@("done" + t.TaskID)" checked="@t.Done" @onchange="(args) => DoneChanged(args, t)" />
<b>@t.Title</b>
...
</li>
}
Listing 6
@foreach (BO.Task t in taskSet)
{
<li @key="t.TaskID" @onclick="() => ShowTaskDetail(t)" … title="Task #@t.TaskID">
...
<input type="checkbox" id="@("done" + t.TaskID)" checked="@t.Done" @onchange="(args) => DoneChanged(args, t)" />
<b>@t.Title</b>
...
</li>
}
Prinzipiell lässt sich statt der Zwei-Wege-Datenbindung mit @ bind="t.Done" auch checked="@t.Done" verwenden. Dann merkt Blazor die Änderungen auch ohne @key. In diesem konkreten Fall ist @bind aber nicht möglich, da das Ereignis @onchange explizit behandelt werden soll. Da @bind bereits @onchange belegt, wird hier die Ein-Wege-Datenbindung mit checked="@t.Done" verwendet.
@key dient auch der Optimierung beim Rendering, wenn ein Teil der Elemente einer Liste erhalten bleibt [3]. @key kann auch verwendet werden, um beim Einfügen von Datensätzen aus dem Hintergrund die aktuelle Cursorposition des Benutzers zu erhalten [4].
Dr. Holger Schwichtenberg (MVP) – alias „Der DOTNET-DOKTOR“ – gehört zu den bekanntesten .NET- und Webexperten in Deutschland. Er ist Chief Technology Expert bei der Softwaremanufaktur MAXIMAGO. Mit dem Expertenteam bei www.IT-Visions.de bietet er zudem Beratung und Schulungen für andere Unternehmen an. Seit 1998 ist er ununterbrochen Sprecher auf jeder BASTA!-Konferenz und Autor von mehr als 70 Fachbüchern.