Virtualizzazione dei componenti di Razor base ASP.NET

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo illustra come usare la virtualizzazione dei componenti nelle app ASP.NET Core Blazor .

Virtualizzazione

Migliorare le prestazioni percepite del rendering dei componenti usando il Blazor supporto virtualizzazione predefinito del framework con il Virtualize<TItem> componente. La virtualizzazione è una tecnica per limitare il rendering dell'interfaccia utente solo alle parti attualmente visibili. Ad esempio, la virtualizzazione è utile quando l'app deve eseguire il rendering di un lungo elenco di elementi e deve essere visibile solo un sottoinsieme di elementi in un determinato momento.

Usare il Virtualize<TItem> componente quando:

  • Rendering di un set di elementi di dati in un ciclo.
  • La maggior parte degli elementi non è visibile a causa dello scorrimento.
  • Gli elementi di cui è stato eseguito il rendering hanno le stesse dimensioni.

Quando l'utente scorre fino a un punto arbitrario nell'elenco Virtualize<TItem> di elementi del componente, il componente calcola gli elementi visibili da visualizzare. Non viene eseguito il rendering degli elementi non visualizzati.

Senza virtualizzazione, un elenco tipico potrebbe usare un ciclo C# foreach per eseguire il rendering di ogni elemento in un elenco. Nell'esempio seguente :

  • allFlights è una raccolta di voli aerei.
  • Il FlightSummary componente visualizza i dettagli relativi a ogni anteprima.
  • L'attributo@keydella direttiva mantiene la relazione di ogni FlightSummary componente con il relativo rendering in anteprima da parte dell'oggetto di anteprima del FlightIdvolo.
<div style="height:500px;overflow-y:scroll">
    @foreach (var flight in allFlights)
    {
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    }
</div>

Se la raccolta contiene migliaia di voli, il rendering dei voli richiede molto tempo e gli utenti riscontrano un notevole ritardo dell'interfaccia utente. La maggior parte dei voli non viene visualizzata perché non rientrano nell'altezza dell'elemento <div> .

Anziché eseguire il rendering dell'intero elenco dei voli contemporaneamente, sostituire il foreach ciclo nell'esempio precedente con il Virtualize<TItem> componente :

  • Specificare allFlights come origine di un elemento fisso in Virtualize<TItem>.Items. Solo i voli attualmente visibili vengono visualizzati dal Virtualize<TItem> componente.

    Se una raccolta non generica fornisce gli elementi, ad esempio una raccolta di DataRow, seguire le indicazioni nella sezione Delega provider di elementi per fornire gli elementi.

  • Specificare un contesto per ogni anteprima con il Context parametro . Nell'esempio flight seguente viene usato come contesto, che fornisce l'accesso ai membri di ogni anteprima.

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

Se un contesto non viene specificato con il Context parametro , usare il valore di context nel modello di contenuto dell'elemento per accedere ai membri di ogni versione di anteprima:

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

Componente Virtualize<TItem> :

  • Calcola il numero di elementi di cui eseguire il rendering in base all'altezza del contenitore e alle dimensioni degli elementi di cui è stato eseguito il rendering.
  • Ricalcola e ricompila gli elementi quando l'utente scorre.
  • Recupera solo la sezione di record da un'API esterna che corrisponde all'area attualmente visibile, inclusa l'overscan, quando ItemsProvider viene usata invece di Items (vedere la sezione Delega provider di elementi).

Il contenuto dell'elemento per il Virtualize<TItem> componente può includere:

  • Codice e Razor HTML normale, come illustrato nell'esempio precedente.
  • Uno o più Razor componenti.
  • Combinazione di componenti e Razor HTML/Razor .

Delegato del provider di elementi

Se non si desidera caricare tutti gli elementi in memoria o la raccolta non è un generico ICollection<T>, è possibile specificare un metodo delegato del provider di elementi nel parametro del Virtualize<TItem>.ItemsProvider componente che recupera in modo asincrono gli elementi richiesti su richiesta. Nell'esempio seguente il LoadEmployees metodo fornisce gli elementi al Virtualize<TItem> componente:

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

Il provider di elementi riceve un ItemsProviderRequestoggetto , che specifica il numero necessario di elementi a partire da un indice iniziale specifico. Il provider di elementi recupera quindi gli elementi richiesti da un database o da un altro servizio e li restituisce ItemsProviderResult<TItem> come insieme a un conteggio degli elementi totali. Il provider di elementi può scegliere di recuperare gli elementi con ogni richiesta o memorizzarli nella cache in modo che siano prontamente disponibili.

Un Virtualize<TItem> componente può accettare solo un'origine elemento dai relativi parametri, quindi non tentare di usare contemporaneamente un provider di elementi e assegnare una raccolta a Items. Se entrambi vengono assegnati, viene generata un'eccezione InvalidOperationException quando i parametri del componente vengono impostati in fase di esecuzione.

L'esempio seguente carica i dipendenti da un oggetto EmployeeService (non illustrato):

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);
}

Nell'esempio seguente una raccolta di è una raccolta non generica, quindi viene usato un delegato del provider di DataRow elementi per la virtualizzazione:

<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 indica al componente di ripetere la richiesta dei dati dal relativo ItemsProvideroggetto . Ciò è utile quando cambiano i dati esterni. In genere non è necessario chiamare RefreshDataAsync quando si usa Items.

RefreshDataAsync aggiorna i dati di un Virtualize<TItem> componente senza causare un rerender. Se RefreshDataAsync viene richiamato da un gestore eventi o da un Blazor metodo del ciclo di vita del componente, l'attivazione di un rendering non è necessaria perché un rendering viene attivato automaticamente alla fine del gestore eventi o del metodo del ciclo di vita. Se RefreshDataAsync viene attivato separatamente da un'attività o un evento in background, ad esempio nel delegato seguente ForecastUpdated , chiamare StateHasChanged per aggiornare l'interfaccia utente alla fine dell'attività o dell'evento in background:

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

...

private Virtualize<FetchData>? virtualizeComponent;

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

Nell'esempio precedente:

  • RefreshDataAsync viene chiamato prima per ottenere nuovi dati per il Virtualize<TItem> componente.
  • StateHasChanged viene chiamato per eseguire il rerendere il componente.

Segnaposto

Poiché la richiesta di elementi da un'origine dati remota potrebbe richiedere del tempo, è possibile eseguire il rendering di un segnaposto con contenuto dell'elemento:

  • Usare (Placeholder<Placeholder>...</Placeholder>) per visualizzare il contenuto fino a quando non sono disponibili i dati dell'elemento.
  • Usare Virtualize<TItem>.ItemContent per impostare il modello di elemento per l'elenco.
<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>

Contenuto vuoto

Usare il EmptyContent parametro per fornire contenuto quando il componente è stato caricato ed Items è vuoto o ItemsProviderResult<TItem>.TotalItemCount è zero.

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();
}

Modificare l'espressione lambda del OnInitialized metodo per visualizzare le stringhe di visualizzazione del componente:

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

Dimensioni dell'elemento

L'altezza di ogni elemento in pixel può essere impostata con Virtualize<TItem>.ItemSize (impostazione predefinita: 50). L'esempio seguente modifica l'altezza di ogni elemento dal valore predefinito di 50 pixel a 25 pixel:

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

Per impostazione predefinita, il Virtualize<TItem> componente misura le dimensioni di rendering (altezza) dei singoli elementi dopo il rendering iniziale. Usare ItemSize per fornire in anticipo una dimensione esatta dell'elemento per facilitare le prestazioni di rendering iniziali accurate e garantire la posizione di scorrimento corretta per i ricaricamenti della pagina. Se il valore predefinito ItemSize causa il rendering di alcuni elementi all'esterno della visualizzazione attualmente visibile, viene attivato un secondo rendering. Per mantenere correttamente la posizione di scorrimento del browser in un elenco virtualizzato, il rendering iniziale deve essere corretto. In caso contrario, gli utenti potrebbero visualizzare gli elementi errati.

Conteggio overscan

Virtualize<TItem>.OverscanCount determina il numero di elementi aggiuntivi di cui viene eseguito il rendering prima e dopo l'area visibile. Questa impostazione consente di ridurre la frequenza di rendering durante lo scorrimento. Tuttavia, i valori più elevati comportano il rendering di più elementi nella pagina (impostazione predefinita: 3). Nell'esempio seguente il numero di overscan viene modificato dal valore predefinito di tre elementi a quattro elementi:

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

Modifiche stato

Quando si apportano modifiche agli elementi di cui viene eseguito il rendering dal Virtualize<TItem> componente, chiamare StateHasChanged per forzare la rivalutazione e il rerendering del componente. Per altre informazioni, vedere Rendering dei componenti di ASP.NET CoreRazor.

Supporto per lo scorrimento della tastiera

Per consentire agli utenti di scorrere il contenuto virtualizzato usando la tastiera, assicurarsi che gli elementi virtualizzati o il contenitore di scorrimento stesso siano attivabili. Se non si esegue questo passaggio, lo scorrimento della tastiera non funziona nei browser basati su Chromium.

Ad esempio, è possibile usare un tabindex attributo nel contenitore di scorrimento:

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

Per altre informazioni sul significato del valore , o di altri valori, vederetabindexla documentazione di MDN. 0-1tabindex

Stili avanzati e rilevamento dello scorrimento

Il Virtualize<TItem> componente è progettato solo per supportare meccanismi di layout di elementi specifici. Per comprendere il corretto funzionamento dei layout degli elementi, di seguito viene illustrato come Virtualize rilevare quali elementi devono essere visibili per la visualizzazione nella posizione corretta.

Se il codice sorgente è simile al seguente:

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

In fase di esecuzione, il componente esegue il Virtualize<TItem> rendering di una struttura DOM simile alla seguente:

<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>

Il numero effettivo di righe di cui è stato eseguito il rendering e le dimensioni degli spaziatori variano in base allo stile e Items alle dimensioni della raccolta. Si noti tuttavia che sono presenti elementi spacer div inseriti prima e dopo il contenuto. Questi servono due scopi:

  • Per fornire un offset prima e dopo il contenuto, causando la visualizzazione degli elementi attualmente visibili nella posizione corretta nell'intervallo di scorrimento e l'intervallo di scorrimento stesso per rappresentare le dimensioni totali di tutto il contenuto.
  • Per rilevare quando l'utente scorre oltre l'intervallo visibile corrente, il che significa che è necessario eseguire il rendering di contenuto diverso.

Nota

Per informazioni su come controllare il tag di elemento HTML dello spacer, vedere la sezione Controllare il nome del tag dell'elemento spacer più avanti in questo articolo.

Gli elementi dello spacer usano internamente un osservatore di intersezione per ricevere una notifica quando diventano visibili. Virtualize dipende dalla ricezione di questi eventi.

Virtualize funziona in base alle condizioni seguenti:

  • Tutti gli elementi di contenuto di cui è stato eseguito il rendering, incluso il contenuto segnaposto, hanno un'altezza identica. In questo modo è possibile calcolare il contenuto corrispondente a una determinata posizione di scorrimento senza prima recuperare ogni elemento di dati e eseguire il rendering dei dati in un elemento DOM.

  • Entrambi gli spaziatori e le righe di contenuto vengono sottoposti a rendering in un singolo stack verticale con ogni elemento che riempie l'intera larghezza orizzontale. Si tratta in genere dell'impostazione predefinita. In casi tipici con div elementi, Virtualize funziona per impostazione predefinita. Se si usa CSS per creare un layout più avanzato, tenere presenti i requisiti seguenti:

    • Lo stile del contenitore di scorrimento richiede uno display con uno dei valori seguenti:
      • block (impostazione predefinita per un oggetto div).
      • table-row-group (impostazione predefinita per un oggetto tbody).
      • flex con flex-direction impostato su column. Assicurarsi che gli elementi figlio immediati del Virtualize<TItem> componente non si compattano in base alle regole di flessibilità. Ad esempio, aggiungere .mycontainer > div { flex-shrink: 0 }.
    • Lo stile delle righe del contenuto richiede uno display dei valori seguenti:
      • block (impostazione predefinita per un oggetto div).
      • table-row (impostazione predefinita per un oggetto tr).
    • Non usare CSS per interferire con il layout per gli elementi dello spacer. Per impostazione predefinita, gli elementi dello spaziore hanno un display valore , blocktranne se l'elemento padre è un gruppo di righe di tabella, nel qual caso l'impostazione predefinita è table-row. Non tentare di influenzare la larghezza o l'altezza degli elementi dello spaziore, inclusa la loro presenza di un bordo o content di pseudoelementi.

Qualsiasi approccio che impedisce il rendering degli spazi e degli elementi di contenuto come singolo stack verticale o determina la variazione dell'altezza degli elementi di contenuto, impedisce il corretto funzionamento del Virtualize<TItem> componente.

Virtualizzazione a livello radice

Il Virtualize<TItem> componente supporta l'uso del documento stesso come radice di scorrimento, in alternativa alla presenza di un altro elemento con overflow-y: scroll. Nell'esempio seguente gli <html> elementi o <body> vengono stiliti in un componente con overflow-y: scroll:

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

Il Virtualize<TItem> componente supporta l'uso del documento stesso come radice di scorrimento, in alternativa alla presenza di un altro elemento con overflow-y: scroll. Quando si usa il documento come radice di scorrimento, evitare di applicare uno stile agli <html> elementi o <body> perché overflow-y: scroll l'osservatore di intersezione considera l'altezza scorrevole completa della pagina come area visibile, anziché solo il riquadro di visualizzazione della finestra.

È possibile riprodurre questo problema creando un elenco virtualizzato di grandi dimensioni (ad esempio 100.000 elementi) e tentando di usare il documento come radice di scorrimento con html { overflow-y: scroll } negli stili CSS della pagina. Anche se può funzionare correttamente a volte, il browser tenta di eseguire il rendering di tutti i 100.000 elementi almeno una volta all'inizio del rendering, che può causare un blocco delle schede del browser.

Per risolvere questo problema prima del rilascio di .NET 7, evitare di applicare stili <html>/<body> agli elementi con overflow-y: scroll o adottare un approccio alternativo. Nell'esempio seguente l'altezza dell'elemento <html> è impostata su oltre il 100% dell'altezza del riquadro di visualizzazione:

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

Il Virtualize<TItem> componente supporta l'uso del documento stesso come radice di scorrimento, in alternativa alla presenza di un altro elemento con overflow-y: scroll. Quando si usa il documento come radice di scorrimento, evitare di applicare uno stile agli <html> elementi o <body> perché overflow-y: scroll determina che l'altezza completa scorrevole della pagina venga considerata come l'area visibile, anziché solo il riquadro di visualizzazione della finestra.

È possibile riprodurre questo problema creando un elenco virtualizzato di grandi dimensioni (ad esempio 100.000 elementi) e tentando di usare il documento come radice di scorrimento con html { overflow-y: scroll } negli stili CSS della pagina. Anche se può funzionare correttamente a volte, il browser tenta di eseguire il rendering di tutti i 100.000 elementi almeno una volta all'inizio del rendering, che può causare un blocco delle schede del browser.

Per risolvere questo problema prima del rilascio di .NET 7, evitare di applicare stili <html>/<body> agli elementi con overflow-y: scroll o adottare un approccio alternativo. Nell'esempio seguente l'altezza dell'elemento <html> è impostata su oltre il 100% dell'altezza del riquadro di visualizzazione:

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

Controllare il nome del tag dell'elemento spacer

Se il Virtualize<TItem> componente viene inserito all'interno di un elemento che richiede un nome di tag figlio specifico, SpacerElement consente di ottenere o impostare il nome del tag di spacer di virtualizzazione. Il valore predefinito è div. Per l'esempio seguente, il componente esegue il Virtualize<TItem> rendering all'interno di un elemento del corpo della tabella (tbody), quindi l'elemento figlio appropriato per una riga di tabella (tr) viene impostato come spaziatore.

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();
}

Nell'esempio precedente, la radice del documento viene usata come contenitore di scorrimento, quindi gli html elementi e body vengono stiliti con overflow-y: scroll. Per ulteriori informazioni, vedi le seguenti risorse: