Scénarios avancés ASP.NET Core Blazor (arborescence de rendu)

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 décrit le scénario avancé de création d’arborescences de rendu Blazor manuellement avec RenderTreeBuilder.

Avertissement

L’utilisation de RenderTreeBuilder pour créer des composants est un scénario avancé. Un composant mal formé (par exemple, une balise non fermée) peut entraîner un comportement imprévisible. Ce comportement peut inclure un rendu du contenu défectueux, la perte de fonctionnalités d’application et une sécurité compromise.

Créer manuellement une arborescence de rendu (RenderTreeBuilder)

RenderTreeBuilder fournit des méthodes pour manipuler des composants et des éléments, notamment la création manuelle de composants dans du code C#.

Considérez le composant PetDetails suivant, qui peut être restitué manuellement dans un autre composant.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

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

Dans le composant BuiltContent suivant, la boucle de la méthode CreateComponent génère trois composants PetDetails.

Dans les méthodes RenderTreeBuilder avec un numéro de séquence, les numéros de séquence sont des numéros de ligne de code source. L’algorithme de différence Blazor s’appuie sur les numéros de séquence correspondant à des lignes de code distinctes, et non à des appels d’appel distincts. Lors de la création d’un composant avec des méthodes RenderTreeBuilder, codez en dur les arguments des numéros de séquence. L’utilisation d’un calcul ou d’un compteur pour générer le numéro de séquence peut entraîner des performances médiocres. Pour plus d’informations, consultez la section Les numéros de séquence sont liés aux numéros de ligne de code et non à l’ordre d’exécution.

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

Warning

Les types dans Microsoft.AspNetCore.Components.RenderTree autorisent le traitement des résultats des opérations de rendu. Il s’agit des détails internes de l’implémentation du framework Blazor. Ces types doivent être considérés comme instables et susceptibles d’être modifiés dans les versions ultérieures.

Les numéros de séquence sont liés aux numéros de ligne de code et non à l’ordre d’exécution

Les fichiers de composants Razor (.razor) sont toujours compilés. L’exécution du code compilé présente un avantage potentiel par rapport à l’interprétation du code, car l’étape de compilation qui génère le code compilé peut être utilisée pour injecter des informations qui améliorent les performances de l’application au moment de l’exécution.

Un exemple clé de ces améliorations concerne les numéros de séquence. Les numéros de séquence indiquent au runtime quelles sorties proviennent de quelles lignes de code distinctes et ordonnées. Le runtime utilise ces informations pour générer des différences d’arborescence efficaces en temps linéaire, ce qui est beaucoup plus rapide que ce qui est normalement possible pour un algorithme différentiel d’arborescence générale.

Considérez le fichier de composants Razor suivant (.razor) :

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

Second

Le balisage et le contenu texte Razor précédents sont compilés en code C#, comme ce qui suit :

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

builder.AddContent(1, "Second");

Lorsque le code s’exécute pour la première fois et que someFlag est true, le générateur reçoit la séquence dans le tableau suivant.

Sequence Type Données
0 Nœud de texte Premier
1 Nœud de texte Seconde

Imaginez que someFlag devient false et que le balisage est rendu à nouveau. Cette fois, le générateur reçoit la séquence dans le tableau suivant.

Sequence Type Données
1 Nœud de texte Seconde

Lorsque le runtime détermine la différence, il voit que l’élément à la séquence 0 a été supprimé, de sorte qu’il génère le script de modification trivial suivant avec une seule étape :

  • Supprimez le premier nœud de texte.

Le problème de la génération de numéros de séquence par programmation

Imaginez plutôt que vous avez écrit la logique de générateur d’arborescence de rendu suivante :

var seq = 0;

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

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

La première sortie est reflétée dans le tableau suivant.

Sequence Type Données
0 Nœud de texte Premier
1 Nœud de texte Seconde

Ce résultat étant identique au cas précédent, il n’existe aucun problème négatif. someFlag est false sur le deuxième rendu, et la sortie est affichée dans le tableau suivant.

Sequence Type Données
0 Nœud de texte Seconde

Cette fois, l’algorithme différentiel constate que deux modifications se sont produites. L’algorithme génère le script de modification suivant :

  • Remplacez la valeur du premier nœud de texte par Second.
  • Supprimez le deuxième nœud de texte.

La génération des numéros de séquence a perdu toutes les informations utiles sur l’emplacement où les branches et les boucles if/else étaient présentes dans le code d’origine. Cela entraîne une différence deux fois plus longue qu’auparavant.

Il s’agit d’un exemple trivial. Dans des cas plus réalistes avec des structures complexes et profondément imbriquées, et en particulier avec des boucles, le coût des performances est généralement plus élevé. Au lieu d’identifier immédiatement les blocs de boucle ou les branches qui ont été insérés ou supprimés, l’algorithme différentiel doit itérer profondément dans les arborescences de rendu. Cela entraîne généralement la création de scripts de modification plus longs, car l’algorithme différentiel est mal renseigné sur la façon dont les anciennes et nouvelles structures sont liées les unes aux autres.

Conseils et conclusions

  • Les performances de l’application souffrent si les numéros de séquence sont générés dynamiquement.
  • Le framework ne peut pas créer automatiquement ses propres numéros de séquence au moment de l’exécution, car les informations nécessaires n’existent pas, sauf si elles sont capturées au moment de la compilation.
  • N’écrivez pas de longs blocs de logique RenderTreeBuilder implémentée manuellement. Préférez des fichiers .razor et autorisez le compilateur à traiter les numéros de séquence. Si vous ne parvenez pas à éviter une logique de RenderTreeBuilder manuelle, fractionnez les longs blocs de code en petits éléments encapsulés dans des appels OpenRegion/CloseRegion. Chaque région a son propre espace de numéros de séquences distincts. Vous pouvez donc redémarrer à partir de zéro (ou de tout autre nombre arbitraire) à l’intérieur de chaque région.
  • Si les numéros de séquences sont codés en dur, l’algorithme différentiel exige uniquement que les numéros de séquences augmentent en valeur. La valeur initiale et les écarts ne sont pas pertinents. Une option légitime consiste à utiliser le numéro de ligne de code comme numéro de séquence, ou à commencer à zéro et à augmenter de 1 ou de centaines (ou tout intervalle préféré).
  • Blazor utilise des numéros de séquence, tandis que d’autres infrastructures d’interface utilisateur à arborescence différentielle ne les utilisent pas. Le calcul de la différence est beaucoup plus rapide lorsque des numéros de séquence sont utilisés, et Blazor présente l’avantage d’avoir une étape de compilation qui traite automatiquement les numéros de séquence pour les développeurs qui créent des fichiers .razor.