Fortgeschrittene Szenarios zu ASP.NET Core Blazor (Renderstrukturerstellung)

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

In diesem Artikel werden fortgeschrittene Szenarios zum manuellen Erstellen von Blazor-Renderstrukturen mit RenderTreeBuilder erläutert.

Warnung

Die Verwendung von RenderTreeBuilder zum Erstellen von Komponenten ist ein erweitertes Szenario. Eine falsch formatierte Komponente (z. B. ein nicht geschlossenes Markuptag) kann zu undefiniertem Verhalten führen. Nicht definiertes Verhalten umfasst fehlerhaftes Rendern von Inhalten, den Verlust von App-Features und kompromittierte Sicherheit.

Manuelles Erstellen einer Renderstruktur (RenderTreeBuilder)

RenderTreeBuilder stellt Methoden zum Bearbeiten von Komponenten und Elementen bereit. Dazu gehört auch das manuelle Erstellen von Komponenten in C#-Code.

Beachten Sie die folgende PetDetails-Komponente, die manuell in einer anderen Komponente gerendert werden kann.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

@code
{
    [Parameter]
    public string? PetDetailsQuote { get; set; }
}

In der folgenden BuiltContent-Komponente generiert die Schleife in der CreateComponent-Methode drei PetDetails-Komponenten.

In RenderTreeBuilder-Methoden mit einer Sequenznummer sind die Sequenznummern Zeilennummern des Quellcodes. Der diff-Algorithmus von Blazor basiert auf den Sequenznummern, die unterschiedlichen Codezeilen und nicht unterschiedlichen Aufrufen entsprechen. Hartcodieren Sie die Argumente für Sequenznummern, wenn Sie eine Komponente mit RenderTreeBuilder-Methoden erstellen. Die Verwendung einer Berechnung oder eines Leistungszählers zum Generieren der Sequenznummer kann zu schlechter Leistung führen. Weitere Informationen finden Sie im Abschnitt Auf Codezeilennummern und nicht auf die Ausführungsreihenfolge bezogene Sequenznummern.

BuiltContent.razor:

@page "/built-content"

<PageTitle>Built Content</PageTitle>

<h1>Built Content Example</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}
@page "/built-content"

<h1>Build a component</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}

Warnung

Die Typen in Microsoft.AspNetCore.Components.RenderTree ermöglichen die Verarbeitung der Ergebnisse von Renderingvorgängen. Hierbei handelt es sich um interne Details der Blazor-Frameworkimplementierung. Diese Typen sollten als instabil betrachtet werden und in zukünftigen Versionen geändert werden.

Auf Codezeilennummern und nicht auf die Ausführungsreihenfolge bezogene Sequenznummern

Razor-Komponentendateien (.razor) werden stets kompiliert. Das Ausführen von kompiliertem Code besitzt einen potenziellen Vorteil gegenüber dem Interpretieren von Code, da der Kompilierungsschritt, der den kompilierten Code ergibt, verwendet werden kann, um Informationen zu injizieren, die die Leistung der App zur Laufzeit verbessern.

Ein wichtiges Beispiel für diese Verbesserungen sind Sequenznummern. Sequenznummern geben der Laufzeit an, welche Ausgaben aus den unterschiedlichen und geordneten Codezeilen stammen. Diese Informationen werden von der Laufzeit verwendet, um effiziente und zeitlich lineare Strukturunterschiede zu generieren. Diese Methode ist viel schneller als es für einen allgemeinen diff-Strukturalgorithmus normalerweise möglich ist.

Betrachten Sie die folgende Razor-Komponentendatei (.razor):

@if (someFlag)
{
    <text>First</text>
}

Second

Der Razor-Markup-und der Textinhalt oben werden in C#-Code kompiliert, der etwa wie folgt aussieht:

if (someFlag)
{
    builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

Wenn der Code zum ersten Mal ausgeführt wird und someFlag gleich true ist, empfängt der Generator die Sequenz in der folgenden Tabelle.

Sequenz Typ Daten
0 Textknoten First
1 Textknoten Second

Stellen Sie sich vor, dass someFlagfalse wird und das Markup erneut gerendert wird. Dieses Mal empfängt der Generator die Sequenz in der folgenden Tabelle.

Sequenz Typ Daten
1 Textknoten Second

Wenn die Laufzeit einen Vergleich durchführt, erkennt sie, dass das Element an Sequenz 0 entfernt wurde, daher generiert sie das folgende triviale Bearbeitungsskript mit einem einzigen Schritt:

  • Entfernen Sie den ersten Textknoten.

Problem beim programmgesteuerten Generieren von Sequenznummern

Angenommen, Sie haben stattdessen die folgende Buildlogik für die Renderstruktur geschrieben:

var seq = 0;

if (someFlag)
{
    builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

Die erste Ausgabe wird in der folgenden Tabelle widergespiegelt.

Sequenz Typ Daten
0 Textknoten First
1 Textknoten Second

Dieses Ergebnis ist mit dem des vorherigen Falls identisch, sodass keine negativen Probleme aufgetreten sind. someFlag ist beim zweiten Rendering false, und die Ausgabe ist in der folgenden Tabelle zu sehen.

Sequenz Typ Daten
0 Textknoten Second

Dieses Mal erkennt der Vergleichsalgorithmus, dass zwei Änderungen aufgetreten sind. Der Algorithmus generiert das folgende Bearbeitungsskript:

  • Ändern Sie den Wert des ersten Textknotens in Second.
  • Entfernen Sie den zweiten Textknoten.

Durch das Erzeugen der Sequenznummern sind alle nützlichen Informationen dazu verloren gegangen, wo die if/else-Branches und -Schleifen im ursprünglichen Code vorhanden waren. Dies führt zu einem doppelt so langen diff wie zuvor.

Dies ist ein triviales Beispiel. In realistischeren Fällen mit komplexen und tief geschachtelten Strukturen und vor allem mit Schleifen sind die Leistungskosten in der Regel höher. Anstatt sofort festzustellen, welche Schleifenblöcke oder Branches eingefügt oder entfernt wurden, muss der Vergleichsalgorithmus eine tiefe Rekursion der Renderstrukturen durchführen. Dies führt in der Regel dazu, dass längere Bearbeitungsskripts erstellt werden, da der Vergleichsalgorithmus falsch informiert ist, wie sich die alten und neuen Strukturen aufeinander beziehen.

Anleitungen und Schlussfolgerungen

  • Die App-Leistung leidet, wenn Sequenznummern dynamisch generiert werden.
  • Das Framework kann zur Laufzeit automatisch keine eigenen Sequenznummern erstellen, da die erforderlichen Informationen nicht vorhanden sind, es sei denn, sie werden zur Kompilierzeit aufgezeichnet.
  • Schreiben Sie keine langen Blöcke manuell implementierter RenderTreeBuilder-Logik. Bevorzugen Sie .razor-Dateien, und ermöglichen Sie es dem Compiler, die Sequenznummern zu verarbeiten. Wenn Sie manuelle RenderTreeBuilder-Logik nicht vermeiden können, teilen Sie lange Codeblöcke in kleinere Teile auf, die in OpenRegion/CloseRegion-Aufrufe eingebunden werden. Jede Region verfügt über einen eigenen separaten Bereich von Sequenznummern, sodass Sie in jeder Region von 0 (Null; oder von jeder anderen beliebigen Zahl) aus starten können.
  • Wenn Sequenznummern hartcodiert sind, erfordert der diff-Algorithmus nur, dass die Sequenznummern den Wert erhöhen. Die Anfangswerte und Lücken sind irrelevant. Eine legitime Option besteht darin, die Codezeilennummer als Sequenznummer zu verwenden oder von 0 (Null) aus zu starten und Werte in Einer- oder Hunderterschritten (oder einem beliebigen bevorzugten Intervall) zu erhöhen.
  • Blazor verwendet Sequenznummern, während andere Benutzeroberflächenframeworks diese nicht für Verzeichnisvergleiche verwenden. Das Vergleichen ist weitaus schneller, wenn Sequenznummern verwendet werden. Außerdem hat Blazor den Vorteil eines Kompilierungsschritts, der Sequenznummern automatisch für Entwickler verarbeitet, die .razor-Dateien erstellen.