Virtualisation de composants ASP.NET Core Razor

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Cet article explique comment utiliser la virtualisation de composants dans les applications ASP.NET Core Blazor.

Virtualization

Améliorez les performances perçues d’un rendu des composants en utilisant la prise en charge intégrée de la virtualisation de l’infrastructure Blazor avec le composant Virtualize<TItem>. La virtualisation est une technique permettant de limiter le rendu de l’interface utilisateur aux parties actuellement visibles. Par exemple, la virtualisation est utile lorsque l’application doit afficher une longue liste d’éléments et que seul un sous-ensemble d’éléments doit être visible à un moment donné.

Utilisez le composant Virtualize<TItem> quand :

  • Rendu d’un ensemble d’éléments de données dans une boucle.
  • La plupart des éléments ne sont pas visibles en raison du défilement.
  • Les éléments rendus ont la même taille.

Lorsque l’utilisateur fait défiler jusqu’à un point arbitraire dans la liste des éléments du composant Virtualize<TItem>, le composant calcule les éléments visibles à afficher. Les éléments invisibles ne sont pas rendus.

Sans virtualisation, une liste classique peut utiliser une boucle foreach C# pour afficher chaque élément d’une liste. Dans l’exemple suivant :

  • allFlights est une collection de vols d’avion.
  • Le composant FlightSummary affiche des détails sur chaque vol.
  • L’attribut de directive @key conserve la relation de chaque composant FlightSummary à son vol rendu par le FlightId du vol.
<div style="height:500px;overflow-y:scroll">
    @foreach (var flight in allFlights)
    {
        <FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
    }
</div>

Si la collection contient des milliers de vols, le rendu des vols prend beaucoup de temps, et les utilisateurs subissent un ralentissement notable de l’interface utilisateur. La plupart des vols ne sont pas visibles, car ils se trouvent en dehors de la hauteur de l’élément <div>.

Au lieu d’afficher la liste complète des vols en une fois, remplacez la boucle foreach dans l’exemple précédent par le composant Virtualize<TItem> :

  • Spécifiez allFlights comme source d’élément fixe pour Virtualize<TItem>.Items. Seuls les vols actuellement visibles sont rendus par le composant Virtualize<TItem>.

    Si une collection non générique fournit les éléments, par exemple une collection de DataRow, suivez les instructions de la section Délégué du fournisseur d'éléments pour fournir les éléments.

  • Spécifiez un contexte pour chaque vol avec le paramètre Context. Dans l’exemple suivant, flight est utilisé comme contexte, qui fournit l’accès aux membres de chaque vol.

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

Si un contexte n’est pas spécifié avec le paramètre Context, utilisez la valeur de context dans le modèle de contenu d’élément pour accéder aux membres de chaque vol :

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

Le composant Virtualize<TItem> :

  • Calcule le nombre d’éléments à restituer en fonction de la hauteur du conteneur et de la taille des éléments rendus.
  • Recalcule et reformule les éléments à mesure que l’utilisateur défile.
  • Récupère uniquement la tranche d'enregistrements d'une API externe qui correspond à la région actuellement visible, y compris le surbalayage, lorsque ItemsProvider est utilisé à la place de Items (voir la section Délégué du fournisseur d'éléments).

Le contenu de l’élément pour le composant Virtualize<TItem> peut inclure :

  • Code HTML et code Razor bruts, comme le montre l’exemple précédent.
  • Un ou plusieurs composants Razor.
  • Combinaison de composants HTML/Razor et Razor.

Délégué du fournisseur d’éléments

Si vous ne souhaitez pas charger tous les éléments en mémoire ou si la collection n’est pas un ICollection<T> générique, vous pouvez spécifier une méthode de délégué du fournisseur d’éléments au paramètre Virtualize<TItem>.ItemsProvider du composant qui récupère de manière asynchrone les éléments requis à la demande. Dans l’exemple suivant, la méthode LoadEmployees fournit les éléments au composant Virtualize<TItem> :

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

Le fournisseur d’éléments reçoit un ItemsProviderRequest, qui spécifie le nombre d’éléments requis à partir d’un index de début spécifique. Le fournisseur d’éléments récupère ensuite les éléments demandés à partir d’une base de données ou d’un autre service et les retourne en tant que ItemsProviderResult<TItem> avec le nombre total d’éléments. Le fournisseur d’éléments peut choisir de récupérer les éléments avec chaque requête ou de les mettre en cache afin qu’ils soient facilement disponibles.

Un composant Virtualize<TItem> ne peut accepter qu’une seule source d’élément à partir de ses paramètres. N’essayez donc pas simultanément d’utiliser un fournisseur d’éléments et d’affecter une collection à Items. Si les deux sont affectés, une InvalidOperationException est levée lorsque les paramètres du composant sont définis au moment de l’exécution.

L’exemple suivant charge des employés à partir d’un EmployeeService (non illustré) :

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

Dans l’exemple suivant, une collection de DataRow est une collection non générique, de sorte qu’un délégué de fournisseur d’éléments est utilisé pour la virtualisation :

<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 indique au composant de re-demander les données à partir de son ItemsProvider. Cela est utile lorsque des données externes changent. Il n’est généralement pas nécessaire d’appeler RefreshDataAsync lors de l’utilisation de Items.

RefreshDataAsync met à jour les données d’un composant Virtualize<TItem> sans provoquer de nouvelle mise à jour. Si RefreshDataAsync est appelé à partir d’un gestionnaire d’événements Blazor ou d’une méthode de cycle de vie de composant, le déclenchement d’un rendu n’est pas obligatoire, car un rendu est automatiquement déclenché à la fin du gestionnaire d’événements ou de la méthode de cycle de vie. Si RefreshDataAsync est déclenché séparément d’une tâche ou d’un événement en arrière-plan, comme dans le délégué ForecastUpdated suivant, appelez StateHasChanged pour mettre à jour l’interface utilisateur à la fin de la tâche ou de l’événement en arrière-plan :

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

...

private Virtualize<FetchData>? virtualizeComponent;

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

Dans l'exemple précédent :

  • RefreshDataAsync est appelé en premier pour obtenir de nouvelles données pour le composant Virtualize<TItem>.
  • StateHasChanged est appelé pour effectuer à nouveau le rendu du composant.

Espace réservé

Étant donné que la demande d’éléments à partir d’une source de données distante peut prendre un certain temps, vous avez la possibilité de restituer un espace réservé avec le contenu de l’élément :

  • Utilisez Placeholder (<Placeholder>...</Placeholder>) pour afficher le contenu jusqu’à ce que les données d’élément soient disponibles.
  • Utilisez Virtualize<TItem>.ItemContent pour définir le modèle d’élément pour la 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>

Contenu vide

Utilisez le paramètre EmptyContent pour fournir du contenu lorsque le composant a été chargé et que Items est vide ou ItemsProviderResult<TItem>.TotalItemCount égal à zéro.

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

Modifiez la méthode OnInitialized lambda pour consulter les chaînes d’affichage du composant :

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

Taille de l’article

La hauteur de chaque élément en pixels peut être définie avec Virtualize<TItem>.ItemSize (valeur par défaut : 50). L’exemple suivant modifie la hauteur de chaque élément des 50 pixels par défaut à 25 pixels :

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

Par défaut, le composant Virtualize<TItem> mesure la taille de rendu (hauteur) des éléments individuels après le rendu initial. Utilisez ItemSize pour fournir une taille d’élément exacte à l’avance pour faciliter des performances de rendu initiales précises et garantir une position de défilement correcte pour les rechargements de page. Si la valeur par défaut ItemSize entraîne le rendu de certains éléments en dehors de l’affichage actuellement visible, un deuxième rendu est déclenché. Pour maintenir correctement la position de défilement du navigateur dans une liste virtualisée, le rendu initial doit être correct. Si ce n’est pas le cas, les utilisateurs pourraient voir les mauvais éléments.

Nombre de suranalyses

Virtualize<TItem>.OverscanCount détermine le nombre d’éléments supplémentaires rendus avant et après la région visible. Ce paramètre permet de réduire la fréquence de rendu pendant le défilement. Toutefois, des valeurs plus élevées entraînent le rendu d’un plus grand nombre d’éléments dans la page (valeur par défaut : 3). L’exemple suivant modifie le nombre de suranalyses de la valeur par défaut de trois éléments à quatre éléments :

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

Modification de l'état

Lorsque vous apportez des modifications aux éléments affichés par le composant Virtualize<TItem>, appelez StateHasChanged pour forcer la réévaluation et la réapprobation du composant. Pour plus d’informations, consultez le rendu de composants Razor ASP.NET Core.

Prise en charge du défilement au clavier

Pour permettre aux utilisateurs de faire défiler du contenu virtualisé à l’aide de leur clavier, assurez-vous que les éléments virtualisés ou le conteneur de défilement lui-même peuvent faire l’objet du focus. Si vous ne parvenez pas à effectuer cette étape, le défilement au clavier ne fonctionne pas dans les navigateurs basés sur Chromium.

Par exemple, vous pouvez utiliser un attribut tabindex sur le conteneur de défilement :

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

Pour en savoir plus sur la signification de la valeur tabindex-1, 0 ou d’autres valeurs, consultez tabindex (documentation MDN).

Styles avancés et détection de défilement

Le composant Virtualize<TItem> est conçu uniquement pour prendre en charge des mécanismes de disposition d’éléments spécifiques. Pour comprendre les dispositions d’éléments qui fonctionnent correctement, l’article suivant explique comment Virtualize détecte les éléments qui doivent être visibles pour être affichés à l’emplacement approprié.

Si votre code source ressemble à ce qui suit :

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

Au moment de l’exécution, le composant Virtualize<TItem> restitue une structure DOM similaire à ce qui suit :

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

Le nombre réel de lignes rendues et la taille des interlignes varient en fonction de votre style et de la taille de la collection Items. Toutefois, notez qu’il existe des éléments d’espacement div injectés avant et après votre contenu. Ils remplissent deux fonctions :

  • Fournir un décalage avant et après votre contenu, ce qui entraîne l’affichage des éléments actuellement visibles à l’emplacement approprié dans la plage de défilement et la plage de défilement elle-même pour représenter la taille totale de tout le contenu.
  • Détecter quand l’utilisateur fait défiler au-delà de la plage visible actuelle, ce qui signifie que du contenu différent doit être rendu.

Remarque

Pour savoir comment contrôler la balise d’élément HTML d’espaceur, consultez la section Contrôle du nom de balise de l’élément d’espacement plus loin dans cet article.

Les éléments d’espacement utilisent un observateur d’intersection en interne pour recevoir une notification lorsqu’ils deviennent visibles. Virtualize dépend de la réception de ces événements.

Virtualize fonctionne dans les conditions suivantes :

  • Tous les éléments de contenu rendus, y compris le contenu d’espace réservé, sont de hauteur identique. Cela permet de calculer le contenu correspondant à une position de défilement donnée sans extraire au préalable chaque élément de données et rendre les données dans un élément DOM.

  • Les interlignes et les lignes de contenu sont rendus dans une seule pile verticale, chaque élément remplissant toute la largeur horizontale. Il s’agit généralement du comportement par défaut. Dans les cas classiques avec des éléments div, Virtualize fonctionne par défaut. Si vous utilisez CSS pour créer une disposition plus avancée, gardez à l’esprit les exigences suivantes :

    • Le style de conteneur de défilement nécessite un display avec l’une des valeurs suivantes :
      • block (par défaut pour div).
      • table-row-group (par défaut pour tbody).
      • flex avec flex-direction défini sur column. Assurez-vous que les enfants immédiats du composant Virtualize<TItem> ne sont pas réduits selon des règles flexibles. Par exemple, ajoutez .mycontainer > div { flex-shrink: 0 }.
    • Le style de ligne de contenu nécessite un display avec l’une des valeurs suivantes :
      • block (par défaut pour div).
      • table-row (par défaut pour tr).
    • N’utilisez pas CSS pour interférer avec la disposition des éléments d’espacement. Par défaut, les éléments de l’espaceur ont une valeur display de block, sauf si le parent est un groupe de lignes de table, auquel cas ils ont la valeur par défaut table-row. N’essayez pas d’influencer la largeur ou la hauteur des éléments d’espacement, notamment en leur faisant avoir une bordure ou des pseudo-éléments content.

Toute approche qui empêche les espacements et les éléments de contenu de s’afficher sous la forme d’une pile verticale unique, ou qui fait varier les éléments de contenu en hauteur, empêche le bon fonctionnement du composant Virtualize<TItem>.

Virtualisation au niveau racine

Le composant Virtualize<TItem> prend en charge l’utilisation du document lui-même comme racine de défilement, comme alternative à l’utilisation d’un autre élément avec overflow-y: scroll. Dans l’exemple suivant, les éléments <html> ou <body> sont mis en forme dans un composant avec overflow-y: scroll :

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

Le composant Virtualize<TItem> prend en charge l’utilisation du document lui-même comme racine de défilement, comme alternative à l’utilisation d’un autre élément avec overflow-y: scroll. Lorsque vous utilisez le document comme racine de défilement, évitez de mettre en forme les éléments <html> ou <body> avec overflow-y: scroll, car cela oblige l’observateur d’intersection à traiter la hauteur de défilement complète de la page comme région visible, au lieu de la fenêtre d’affichage seule.

Vous pouvez reproduire ce problème en créant une grande liste virtualisée (par exemple, 100 000 éléments) et tenter d’utiliser le document comme racine de défilement avec html { overflow-y: scroll } dans les styles CSS de la page. Bien que cela puisse fonctionner correctement parfois, le navigateur tente de restituer les 100 000 éléments au moins une fois au début du rendu, ce qui peut entraîner un verrouillage de l’onglet du navigateur.

Pour contourner ce problème avant la publication de .NET 7, évitez les éléments de style <html>/<body> avec overflow-y: scroll, ou adoptez une autre approche. Dans l’exemple suivant, la hauteur de l’élément <html> est définie sur un peu plus de 100 % de la hauteur de la fenêtre d’affichage :

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

Le composant Virtualize<TItem> prend en charge l’utilisation du document lui-même comme racine de défilement, comme alternative à l’utilisation d’un autre élément avec overflow-y: scroll. Lorsque vous utilisez le document comme racine de défilement, évitez de mettre en forme les éléments <html> ou <body> avec overflow-y: scroll, car cela entraîne le traitement de la hauteur de défilement complète de la page comme zone visible, au lieu de la fenêtre d’affichage seule.

Vous pouvez reproduire ce problème en créant une grande liste virtualisée (par exemple, 100 000 éléments) et tenter d’utiliser le document comme racine de défilement avec html { overflow-y: scroll } dans les styles CSS de la page. Bien que cela puisse fonctionner correctement parfois, le navigateur tente de restituer les 100 000 éléments au moins une fois au début du rendu, ce qui peut entraîner un verrouillage de l’onglet du navigateur.

Pour contourner ce problème avant la publication de .NET 7, évitez les éléments de style <html>/<body> avec overflow-y: scroll, ou adoptez une autre approche. Dans l’exemple suivant, la hauteur de l’élément <html> est définie sur un peu plus de 100 % de la hauteur de la fenêtre d’affichage :

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

Contrôle du nom de balise de l’élément d’espacement

Si le composant Virtualize<TItem> est placé à l’intérieur d’un élément qui nécessite un nom de balise enfant spécifique, SpacerElement vous permet d’obtenir ou de définir le nom de la balise d’espaceur de virtualisation. La valeur par défaut est div. Pour l’exemple suivant, le composant Virtualize<TItem> s’affiche à l’intérieur d’un élément de corps de table (tbody), de sorte que l’élément enfant approprié pour une ligne de table (tr) est défini comme espaceur.

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

Dans l’exemple précédent, la racine du document est utilisée comme conteneur de défilement, de sorte que les éléments html et body sont mis en forme avec overflow-y: scroll. Pour plus d'informations, reportez-vous aux ressources suivantes :