ASP.NET Core Razor-Komponentenvirtualisierung

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 wird erläutert, wie die Komponentenvirtualisierung in ASP.NET Core Blazor-Apps verwendet wird.

Virtualisierung

Verbessern Sie die gefühlte Leistung des Komponentenrenderings mithilfe der integrierten Virtualisierungsunterstützung des Blazor-Frameworks, indem Sie die Virtualize<TItem>-Komponente verwenden. Virtualisierung ist eine Technik zum Einschränken des Benutzeroberflächenrenderings auf die aktuell sichtbaren Elemente. Die Virtualisierung ist beispielsweise hilfreich, wenn die App eine lange Liste von Elementen rendern und nur eine Teilmenge der Elemente zu einem bestimmten Zeitpunkt sichtbar sein muss.

Verwenden Sie die Virtualize<TItem>-Komponente in folgenden Fällen:

  • Rendern eines Satzes von Datenelementen in einer Schleife.
  • Die meisten Elemente sind beim Scrollen nicht sichtbar.
  • Die gerenderten Elemente haben die gleiche Größe.

Wenn der Benutzer zu einem beliebigen Punkt in der Liste der Elemente der Virtualize<TItem>-Komponente scrollt, berechnet die Komponente die sichtbaren Elemente, die angezeigt werden müssen. Nicht sichtbare Elemente werden nicht gerendert.

Ohne Virtualisierung kann eine typische Liste eine C#-foreach-Schleife verwenden, um die einzelnen Elemente in einer Liste zu rendern. Im folgenden Beispiel:

  • allFlights ist eine Sammlung von Flügen.
  • Die FlightSummary-Komponente zeigt Details zu jedem Flug an.
  • Das @key-Anweisungsattribut speichert die Beziehung der einzelnen FlightSummary-Komponenten zu den entsprechenden gerenderten Flügen über die FlightId des Flugs.
<div style="height:500px;overflow-y:scroll">
    @foreach (var flight in allFlights)
    {
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    }
</div>

Wenn die Sammlung Tausende von Flügen enthält, dauert das Rendern der Flüge sehr lange, und die Benutzer stellen eine merkliche Verzögerung bei der Reaktion der Benutzeroberfläche fest. Die meisten der Flüge werden nicht angezeigt, weil Sie außerhalb der Höhe des <div>-Elements liegen.

Anstatt die gesamte Liste der Flüge gleichzeitig zu rendern, ersetzen Sie die foreach-Schleife im vorherigen Beispiel durch die Virtualize<TItem>-Komponente:

  • Geben Sie allFlights als Quelle fester Elemente für Virtualize<TItem>.Items an. Nur die aktuell sichtbaren Flüge werden von der Virtualize<TItem>-Komponente gerendert.

    Wenn eine nicht generische Auflistung die Elemente bereitstellt, z. B. eine Auflistung von DataRow, folgen Sie den Anweisungen im Abschnitt Elementanbieterdelegat, um die Elemente bereitzustellen.

  • Geben Sie mit dem Context-Parameter einen Kontext für jeden Flug an. Im folgenden Beispiel wird flight als Kontext verwendet, der den Zugriff auf die Member der einzelnen Flüge ermöglicht.

<div style="height:500px;overflow-y:scroll">
    <Virtualize Items="allFlights" Context="flight">
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    </Virtualize>
</div>

Wenn kein Kontext mit dem Context-Parameter angegeben wird, verwenden Sie den Wert von context in der Elementinhaltsvorlage, um auf die Member der einzelnen Flüge zuzugreifen:

<div style="height:500px;overflow-y:scroll">
    <Virtualize Items="allFlights">
        <FlightSummary @key="context.FlightId" Details="@context.Summary" />
    </Virtualize>
</div>

Die Komponente Virtualize<TItem>:

  • Berechnet die Anzahl der zu rendernden Elemente basierend auf der Größe des Containers und der gerenderten Elemente.
  • Führt Neuberechnungen durch, während der Benutzer scrollt, und rendert die Elemente erneut.
  • Ruft nur das Datensegment von Datensätzen aus einer externen API ab, die dem aktuell sichtbaren Bereich entspricht, einschließlich Overscan, wenn ItemsProvider anstelle von Items verwendet wird (siehe den Abschnitt Elementanbieterdelegat).

Der Elementinhalt für die Virtualize<TItem>-Komponente kann Folgendes umfassen:

  • Einfachen HTML- und Razor-Code, wie im vorherigen Beispiel gezeigt
  • Eine oder mehrere Razor-Komponenten
  • Eine Mischung aus HTML/Razor- und Razor-Komponenten.

Elementanbieterdelegat

Wenn Sie nicht alle Elemente in den Arbeitsspeicher laden möchten oder die Auflistung keine generische ICollection<T> ist, können Sie eine Elementanbieter-Delegatmethode für den Virtualize<TItem>.ItemsProvider-Parameter der Komponente festlegen, die die angeforderten Elemente bei Bedarf asynchron abruft. Im folgenden Beispiel stellt die Methode LoadEmployees die Elemente für die Komponente Virtualize<TItem> bereit:

<Virtualize Context="employee" ItemsProvider="LoadEmployees">
    <p>
        @employee.FirstName @employee.LastName has the 
        job title of @employee.JobTitle.
    </p>
</Virtualize>

Der Elementanbieter empfängt eine ItemsProviderRequest, die die erforderliche Anzahl von Elementen angibt, beginnend an einem bestimmten Startindex. Der Elementanbieter ruft dann die angeforderten Elemente aus einer Datenbank oder einem anderen Dienst ab und gibt Sie als ItemsProviderResult<TItem> zusammen mit der Gesamtzahl der Elemente zurück. Der Elementanbieter kann auswählen, ob die Elemente mit jeder Anforderung abgerufen oder zwischengespeichert werden, sodass Sie sofort verfügbar sind.

Eine Virtualize<TItem>-Komponente kann nur eine Elementquelle aus ihren Parametern akzeptieren. Versuchen Sie daher nicht, gleichzeitig einen Anbieter eines Elements zu verwenden und Itemseine Sammlung zuzuweisen. Wenn beide zugewiesen sind, wird eine InvalidOperationException ausgelöst, wenn die Parameter der Komponente zur Laufzeit festgelegt werden.

Im folgenden Beispiel werden Mitarbeiter aus einem EmployeeService (nicht gezeigt) geladen:

private async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(
    ItemsProviderRequest request)
{
    var numEmployees = Math.Min(request.Count, totalEmployees - request.StartIndex);
    var employees = await EmployeesService.GetEmployeesAsync(request.StartIndex, 
        numEmployees, request.CancellationToken);

    return new ItemsProviderResult<Employee>(employees, totalEmployees);
}

Im folgenden Beispiel ist eine Auflistung von DataRow eine nicht generische Auflistung, sodass ein Elementanbieterdelegat für Virtualisierung verwendet wird:

<Virtualize Context="row" ItemsProvider="GetRows">
    ...
</Virtualize>

@code{
    ...

    private ValueTask<ItemsProviderResult<DataRow>> GetRows(ItemsProviderRequest request)
    {
        return new(new ItemsProviderResult<DataRow>(
            dataTable.Rows.OfType<DataRow>().Skip(request.StartIndex).Take(request.Count),
            dataTable.Rows.Count));
    }
}

Virtualize<TItem>.RefreshDataAsync weist die Komponente an, Daten noch mal von ItemsProvider anzufordern. Dies ist nützlich, wenn sich externe Daten ändern. RefreshDataAsync muss normalerweise nicht aufgerufen werden, wenn Items verwendet wird.

RefreshDataAsync aktualisiert die Daten einer Virtualize<TItem>-Komponente, ohne ein Rerendering auszulösen. Wenn RefreshDataAsync von einem Blazor-Ereignishandler oder einer Lebenszyklusmethode für Komponenten aufgerufen wird, ist das Auslösen eines Renderings nicht erforderlich, da nach dem Ausführen des Ereignishandlers oder der Lebenszyklusmethode automatisch ein Rendering ausgelöst wird. Wenn RefreshDataAsync unabhängig von einer Hintergrundaufgabe oder einem Hintergrundereignis ausgelöst wird, wie etwa im folgenden ForecastUpdated-Delegaten, rufen Sie StateHasChanged auf, sodass die Benutzeroberfläche nach dem Ausführen der Hintergrundaufgabe oder nach dem Hintergrundereignis aktualisiert wird:

<Virtualize ... @ref="virtualizeComponent">
    ...
</Virtualize>

...

private Virtualize<FetchData>? virtualizeComponent;

protected override void OnInitialized()
{
    WeatherForecastSource.ForecastUpdated += async () => 
    {
        await InvokeAsync(async () =>
        {
            await virtualizeComponent?.RefreshDataAsync();
            StateHasChanged();
        });
    });
}

Im vorherigen Beispiel:

  • RefreshDataAsync wird zuerst aufgerufen, um neue Daten für die Virtualize<TItem>-Komponente abzurufen.
  • StateHasChanged wird aufgerufen, um die Komponente erneut zu rendern.

Platzhalter

Da das Anfordern von Elementen von einer Remotedatenquelle etwas Zeit in Anspruch nehmen kann, verfügen Sie über die Option, einen Platzhalter mit Elementinhalt zu rendern:

  • Verwenden Sie Placeholder (<Placeholder>...</Placeholder>), um Inhalt anzuzeigen, bis die Elementdaten verfügbar sind.
  • Verwenden Sie Virtualize<TItem>.ItemContent zum Festlegen der Elementvorlage für die Liste.
<Virtualize Context="employee" ItemsProvider="LoadEmployees">
    <ItemContent>
        <p>
            @employee.FirstName @employee.LastName has the 
            job title of @employee.JobTitle.
        </p>
    </ItemContent>
    <Placeholder>
        <p>
            Loading&hellip;
        </p>
    </Placeholder>
</Virtualize>

Leerer Inhalt

Verwenden Sie den Parameter EmptyContent, um Inhalte bereitzustellen, wenn die Komponente geladen wurde und entweder Items leer oder ItemsProviderResult<TItem>.TotalItemCount null ist.

EmptyContent.razor:

@page "/empty-content"

<PageTitle>Empty Content</PageTitle>

<h1>Empty Content Example</h1>

<Virtualize Items="@stringList">
    <ItemContent>
        <p>
            @context
        </p>
    </ItemContent>
    <EmptyContent>
        <p>
            There are no strings to display.
        </p>
    </EmptyContent>
</Virtualize>

@code {
    private List<string>? stringList;

    protected override void OnInitialized() => stringList ??= new();
}

Ändern Sie die Lambdafunktion der OnInitialized-Methode, damit die Komponente Zeichenfolgen anzeigt:

protected override void OnInitialized() =>
    stringList ??= new() { "Here's a string!", "Here's another string!" };

Elementgröße

Die Höhe jedes Elements in Pixel kann mit Virtualize<TItem>.ItemSize festgelegt werden (Standardwert: 50). Im folgenden Beispiel wird die Höhe jedes Elements vom Standardwert von 50 Pixel in 25 Pixel geändert:

<Virtualize Context="employee" Items="employees" ItemSize="25">
    ...
</Virtualize>

Die Virtualize<TItem>-Komponente misst die Renderinggröße (Höhe) einzelner Elemente standardmäßig nach dem ersten Rendering. Verwenden Sie ItemSize, um die genaue Elementgröße im Voraus anzugeben, um die Leistung beim ersten Rendering zu verbessern und die richtige Scrollposition für das erneute Laden von Webseiten sicherzustellen. Wenn die Standardvorgehensweise ItemSize zur Folge hat, dass einige Elemente außerhalb der sichtbaren Ansicht gerendert werden, wird ein zweites Rendering ausgelöst. Damit die Scrollposition des Browsers in einer virtualisierten Liste korrekt beibehalten wird, muss das erste Rendering korrekt sein. Andernfalls werden Benutzern ggf. falsche Elemente angezeigt.

Overscananzahl

Virtualize<TItem>.OverscanCount bestimmt, wie viele zusätzliche Elemente vor und nach dem sichtbaren Bereich gerendert werden. Diese Einstellung trägt dazu bei, die Häufigkeit des Renderns beim Scrollen zu verringern. Höhere Werte führen jedoch dazu, dass mehr Elemente auf der Seite gerendert werden (Standardwert: 3). Im folgenden Beispiel wird die Overscananzahl vom Standardwert von drei Elementen in vier Elemente geändert:

<Virtualize Context="employee" Items="employees" OverscanCount="4">
    ...
</Virtualize>

Statusänderungen

Wenn Sie Änderungen an Elementen vornehmen, die von der Virtualize<TItem>-Komponente gerendert werden, rufen Sie StateHasChanged auf, um die erneute Auswertung und das erneute Rendern der Komponente zu erzwingen. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Unterstützung für Scrollen über die Tastatur

Damit Benutzer*innen virtualisierte Inhalte mithilfe der Tastatur scrollen können, sorgen Sie dafür, dass die virtualisierten Elemente oder der Scrollcontainer selbst fokussierbar sind. Wenn Sie diesen Schritt nicht durchführen, funktioniert das Scrollen über die Tastatur in Chromium-basierten Browsern nicht.

Beispielsweise können Sie ein tabindex-Attribut für den Scrollcontainer verwenden:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <Virtualize Items="allFlights">
        <div class="flight-info">...</div>
    </Virtualize>
</div>

Weitere Informationen zur Bedeutung der tabindex-Werte -1, 0 oder anderer Werte finden Sie unter tabindex (MDN-Dokumentation).

Erweiterte Stile und Scrollerkennung

Die Virtualize<TItem>-Komponente dient lediglich der Unterstützung bestimmter Elementlayoutmechanismen. Damit Sie erkennen können, welche Elementlayouts ordnungsgemäß funktionieren, wird im Folgenden erläutert, wie Virtualize erkennt, welche Elemente für die Anzeige an der richtigen Stelle sichtbar sein müssen.

Wenn Ihr Quellcode wie folgt aussieht:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <Virtualize Items="allFlights" ItemSize="100">
        <div class="flight-info">Flight @context.Id</div>
    </Virtualize>
</div>

Die Virtualize<TItem>-Komponente rendert eine DOM-Struktur wie die folgende zur Laufzeit:

<div style="height:500px; overflow-y:scroll" tabindex="-1">
    <div style="height:1100px"></div>
    <div class="flight-info">Flight 12</div>
    <div class="flight-info">Flight 13</div>
    <div class="flight-info">Flight 14</div>
    <div class="flight-info">Flight 15</div>
    <div class="flight-info">Flight 16</div>
    <div style="height:3400px"></div>
</div>

Die tatsächliche Anzahl der gerenderten Zeilen und die Größe der Abstandhalter sind je nach Stil und Items-Sammlungsgröße unterschiedlich. Beachten Sie jedoch, dass div-Abstandhalterelemente vor und nach dem Inhalt eingefügt werden. Diese haben zwei Aufgaben:

  • Sie sollen vor und nach dem Inhalt einen Offset bereitstellen, wodurch derzeit sichtbare Elemente an der richtigen Stelle im Scrollbereich angezeigt werden und im Scrollbereich selbst die Gesamtgröße des gesamten Inhalts dargestellt wird.
  • Sie sollen erkennen, wenn Benutzer*innen über den aktuell sichtbaren Bereich hinaus scrollen. Das bedeutet, dass verschiedene Inhalte gerendert werden müssen.

Hinweis

Informationen zum Steuern des Tags für das HTML-Abstandhalterelement finden Sie weiter unten in diesem Artikel im Abschnitt Steuern des Tagnamens für das Abstandhalterelement.

Die Abstandhalterelemente verwenden intern eine API vom Typ Intersection Observer, um Benachrichtigungen zu erhalten, wenn sie sichtbar werden. Virtualize ist auf den Empfang dieser Ereignisse angewiesen.

Virtualize funktioniert unter den folgenden Bedingungen:

  • Alle gerenderten Inhaltselemente, einschließlich Platzhalterinhalte, weisen die gleiche Höhe auf. Dadurch kann berechnet werden, welcher Inhalt einer bestimmten Scrollposition entspricht, ohne zuerst jedes Datenelement abrufen und die Daten in ein DOM-Element rendern zu müssen.

  • Sowohl die Abstandhalter als auch die Inhaltszeilen werden in einem einzelnen vertikalen Stapel gerendert, bei dem jedes Element die gesamte horizontale Breite einnimmt. Dies ist im Allgemeinen die Standardeinstellung. Üblicherweise funktioniert Virtualize bei div-Elementen standardmäßig. Wenn Sie zum Erstellen eines anspruchsvolleren Layouts CSS verwenden, sollten Sie die folgenden Anforderungen beachten:

    • Für die Formatierung von Scrollcontainern ist ein display mit einem der folgenden Werte erforderlich:
      • block (Standardwert für div).
      • table-row-group (Standardwert für tbody).
      • flex, wobei flex-direction auf column festgelegt ist. Stellen Sie sicher, dass die unmittelbar untergeordneten Elemente der Komponente Virtualize<TItem> bei flexiblen Regeln nicht verkleinert werden. Fügen Sie beispielsweise .mycontainer > div { flex-shrink: 0 } hinzu.
    • Für das Formatieren von Inhaltszeilen ist ein display mit einem der folgenden Werte erforderlich:
      • block (Standardwert für div).
      • table-row (Standardwert für tr).
    • Verwenden Sie kein CSS, um in das Layout der Abstandhalterelemente einzugreifen. Standardmäßig haben die Abstandhalterelemente den display-Wert block, es sei denn, das übergeordnete Element ist eine Tabellenzeilengruppe. In diesem Fall wird standardmäßig table-row verwendet. Versuchen Sie nicht, die Breite oder Höhe des Abstandhalterelements zu beeinflussen, auch nicht, indem Sie einen Rahmen oder content-Pseudoelemente hinzufügen.

Jeder Ansatz, der das Rendern der Abstandhalter und Inhaltselemente als einzelnen vertikalen Stapel verhindert oder dazu führt, dass die Inhaltselemente in der Höhe variieren, verhindert, dass die Virtualize<TItem>-Komponente ordnungsgemäß funktioniert.

Virtualisierung auf Stammebene

Die Virtualize<TItem>-Komponente unterstützt die Verwendung des eigentlichen Dokuments als Scrollstamm (alternativ zur Verwendung eines anderen Elements mit overflow-y: scroll). Im folgenden Beispiel werden die <html>- oder <body>-Elemente in einer Komponente mit overflow-y: scroll formatiert:

<HeadContent>
    <style>
        html, body { overflow-y: scroll }
    </style>
</HeadContent>

Die Virtualize<TItem>-Komponente unterstützt die Verwendung des eigentlichen Dokuments als Scrollstamm (alternativ zur Verwendung eines anderen Elements mit overflow-y: scroll). Wenn Sie das Dokument als Scrollstamm verwenden, vermeiden Sie es, die Elemente vom Typ <html> oder <body> mit overflow-y: scroll zu formatieren, da dies dazu führt, dass der Schnittpunktbeobachter die vollständige scrollbare Höhe der Seite als sichtbaren Bereich behandelt, anstatt nur den Viewport des Fensters.

Sie können dieses Problem reproduzieren, indem Sie eine große virtualisierte Liste (z. B. 100.000 Elemente) erstellen und versuchen, das Dokument als Scrollstamm mit html { overflow-y: scroll } in den CSS-Formatvorlagen der Seite zu verwenden. Das funktioniert zwar manchmal ordnungsgemäß, der Browser versucht jedoch mindestens einmal am Anfang des Renderings, alle 100.000 Elemente zu rendern, was dazu führen kann, dass der Browser-Tab nicht mehr reagiert.

Sehen Sie entweder davon ab, Elemente vom Typ <html>/<body> mit overflow-y: scroll zu formatieren, oder verwenden sie einen alternativen Ansatz, um dieses Problem vor dem Release von .NET 7 zu umgehen. Im folgenden Beispiel wird die Höhe des <html>-Elements auf etwas über 100 Prozent der Viewporthöhe festgelegt:

<HeadContent>
    <style>
        html { min-height: calc(100vh + 0.3px) }
    </style>
</HeadContent>

Die Virtualize<TItem>-Komponente unterstützt die Verwendung des eigentlichen Dokuments als Scrollstamm (alternativ zur Verwendung eines anderen Elements mit overflow-y: scroll). Wenn Sie das Dokument als Scrollstamm verwenden, vermeiden Sie es, die Elemente vom Typ <html> oder <body> mit overflow-y: scroll zu formatieren, da dies dazu führt, dass die vollständige scrollbare Höhe der Seite als sichtbarer Bereich behandelt wird (nicht nur der Viewport des Fensters).

Sie können dieses Problem reproduzieren, indem Sie eine große virtualisierte Liste (z. B. 100.000 Elemente) erstellen und versuchen, das Dokument als Scrollstamm mit html { overflow-y: scroll } in den CSS-Formatvorlagen der Seite zu verwenden. Das funktioniert zwar manchmal ordnungsgemäß, der Browser versucht jedoch mindestens einmal am Anfang des Renderings, alle 100.000 Elemente zu rendern, was dazu führen kann, dass der Browser-Tab nicht mehr reagiert.

Sehen Sie entweder davon ab, Elemente vom Typ <html>/<body> mit overflow-y: scroll zu formatieren, oder verwenden sie einen alternativen Ansatz, um dieses Problem vor dem Release von .NET 7 zu umgehen. Im folgenden Beispiel wird die Höhe des <html>-Elements auf etwas über 100 Prozent der Viewporthöhe festgelegt:

<style>
    html { min-height: calc(100vh + 0.3px) }
</style>

Steuern des Tagnamens für das Abstandhalterelement

Wenn die Virtualize<TItem>-Komponente innerhalb eines Elements platziert wird, das einen bestimmten untergeordneten Tagnamen erfordert, ermöglicht SpacerElement das Abrufen oder Festlegen des Tagnamens für den Virtualisierungsabstandhalter. Standardwert: div. Im folgenden Beispiel rendert die Virtualize<TItem>-Komponente innerhalb eines Tabellentextelements (tbody). Daher wird das entsprechende untergeordnete Element für eine Tabellenzeile (tr) als Abstandhalter festgelegt.

VirtualizedTable.razor:

@page "/virtualized-table"

<PageTitle>Virtualized Table</PageTitle>

<HeadContent>
    <style>
        html, body {
            overflow-y: scroll
        }
    </style>
</HeadContent>

<h1>Virtualized Table Example</h1>

<table id="virtualized-table">
    <thead style="position: sticky; top: 0; background-color: silver">
        <tr>
            <th>Item</th>
            <th>Another column</th>
        </tr>
    </thead>
    <tbody>
        <Virtualize Items="fixedItems" ItemSize="30" SpacerElement="tr">
            <tr @key="context" style="height: 30px;" id="row-@context">
                <td>Item @context</td>
                <td>Another value</td>
            </tr>
        </Virtualize>
    </tbody>
</table>

@code {
    private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}

Im vorherigen Beispiel wird der Dokumentstamm als Scrollcontainer verwendet. Daher werden die html- und body-Elemente mit overflow-y: scroll formatiert. Weitere Informationen finden Sie in den folgenden Ressourcen: