Prise en charge des types génériques de composants Razor ASP.NET Core

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 la prise en charge des types génériques dans les composants Razor.

Si vous débutez avec les types génériques, consultez Classes et méthodes génériques (guide C#) pour obtenir des conseils généraux sur l’utilisation des génériques avant de lire cet article.

L’exemple de code de cet article est disponible uniquement pour la dernière version de .NET dans les exemples d’applications Blazor.

Prise en charge des paramètres de type générique

La directive @typeparam déclare un paramètre de type générique pour la classe de composant générée :

@typeparam TItem

La syntaxe C# avec les contraintes de type where est prise en charge :

@typeparam TEntity where TEntity : IEntity

Dans l’exemple suivant, le composant ListItems1 est de type générique TExample, ce qui représente le type de la collection ExampleList.

ListItems1.razor:

@typeparam TExample

<h2>List Items 1</h2>

@if (ExampleList is not null)
{
    <ul style="color:@Color">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>

    <p>
        Type of <code>TExample</code>: @typeof(TExample)
    </p>
}

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

    [Parameter]
    public IEnumerable<TExample>? ExampleList { get; set; }
}

Le composant suivant génère le rendu de deux ListItems1 composants :

  • Des données de chaîne ou d’entier sont affectées au paramètre ExampleList de chaque composant.
  • Le type string ou int qui correspond au type des données affectées est défini pour le paramètre de type (TExample) de chaque composant.

Generics1.razor:

@page "/generics-1"

<PageTitle>Generics 1</PageTitle>

<h1>Generic Type Example 1</h1>

<ListItems1 Color="blue"
            ExampleList="@(new List<string> { "Item 1", "Item 2" })"
            TExample="string" />

<ListItems1 Color="red"
            ExampleList="@(new List<int> { 1, 2 })"
            TExample="int" />

Pour en savoir plus, consultez les informations de référence sur la syntaxe Razor pour ASP.NET Core. Pour obtenir un exemple de type générique avec des composants basés sur un modèle, consultez les composants basés sur un modèle Blazor ASP.NET Core.

Prise en charge du type générique en cascade

Un composant ancêtre peut passer en cascade un paramètre de type par nom à des descendants avec l’attribut [CascadingTypeParameter]. Cet attribut permet à une inférence de type générique d’utiliser automatiquement le paramètre de type spécifié avec les descendants qui ont un paramètre de type portant le même nom.

Si vous ajoutez @attribute [CascadingTypeParameter(...)] à un composant, l’argument de type générique spécifié est automatiquement utilisé par les descendants qui :

  • Sont imbriqués comme contenu enfant pour le composant dans le même document .razor.
  • Déclarent également un @typeparam avec le même nom.
  • N’ont pas d’autre valeur explicitement fournie ou implicitement déduite pour le paramètre de type. Si une autre valeur est fournie ou déduite, elle a priorité sur le type générique en cascade.

Lors de la réception d’un paramètre de type cascade, les composants obtiennent la valeur de paramètre de l’ancêtre le plus proche qui a un attribut [CascadingTypeParameter] avec un nom correspondant. Les paramètres de type générique en cascade sont remplacés dans une sous-arborescence particulière.

La correspondance n’est effectuée que par nom. Nous vous recommandons donc d’éviter d’utiliser un paramètre de type générique en cascade avec un nom générique, par exemple T ou TItem. Si un développeur choisit de passer en cascade un paramètre de type, il promet implicitement que son nom est suffisamment unique pour ne pas entrer en conflit avec d’autres paramètres de type en cascade provenant de composants non liés.

Les types génériques peuvent être transmis en cascade à des composants enfants selon l’une des approches suivantes avec des composants ancêtres (parents). Ces approches sont présentées dans les deux sous-sections suivantes :

  • Définir explicitement le type générique en cascade.
  • Déduire explicitement le type générique en cascade.

Les sous-sections suivantes fournissent des exemples des approches précédentes en utilisant le composant ListDisplay1 suivant. Le composant reçoit et affiche les données de liste de type générique en tant que TExample. Pour que chaque instance de ListDisplay1 se distingue, un paramètre de composant supplémentaire contrôle la couleur de la liste.

ListDisplay1.razor:

@typeparam TExample

@if (ExampleList is not null)
{
    <ul style="color:@Color">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

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

    [Parameter]
    public IEnumerable<TExample>? ExampleList { get; set; }
}

Types génériques explicites basés sur des composants ancêtres

La démonstration dans cette section passe en cascade un type de manière explicite pour TExample.

Remarque

Cette section utilise le composant ListDisplay1 précédent de la section Prise en charge des types génériques en cascade.

Le composant ListItems2 suivant reçoit des données et passe en cascade un paramètre de type générique nommé TExample à ses composants descendants. Dans le composant parent à venir, le composant ListItems2 est utilisé pour afficher des données de liste avec le composant ListDisplay1 précédent.

ListItems2.razor:

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Items 2</h2>

@ChildContent

<p>
    Type of <code>TExample</code>: @typeof(TExample)
</p>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Le composant parent suivant définit le contenu enfant (RenderFragment) de deux ListItems2 composants spécifiant les ListItems2 types (TExample), qui sont passés en cascade aux composants enfants. Les composants ListDisplay1 sont rendus avec les données d’élément de liste présentées dans l’exemple. Les données de type chaîne sont utilisées avec le premier composant ListItems2 tandis que les données de type int sont utilisées avec le second composant ListItems2.

Generics2.razor :

@page "/generics-2"

<PageTitle>Generics 2</PageTitle>

<h1>Generic Type Example 2</h1>

<ListItems2 TExample="string">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListItems2>

<ListItems2 TExample="int">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<int> { 1, 2 })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<int> { 3, 4 })" />
</ListItems2>

La spécification explicite du type permet également l’utilisation de valeurs et paramètres en cascade pour fournir des données aux composants enfants, comme le montre la démonstration suivante.

ListDisplay2.razor :

@typeparam TExample

@if (ExampleList is not null)
{
    <ul style="color:@Color">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

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

    [CascadingParameter]
    protected IEnumerable<TExample>? ExampleList { get; set; }
}

ListItems3.razor :

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Items 3</h2>

@ChildContent

@if (ExampleList is not null)
{
    <ul style="color:green">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>

    <p>
        Type of <code>TExample</code>: @typeof(TExample)
    </p>
}

@code {
    [CascadingParameter]
    protected IEnumerable<TExample>? ExampleList { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Lors du passage en cascade des données dans l’exemple suivant, le type doit être fourni au composant .

Generics3.razor:

@page "/generics-3"

<PageTitle>Generics 3</PageTitle>

<h1>Generic Type Example 3</h1>

<CascadingValue Value="stringData">
    <ListItems3 TExample="string">
        <ListDisplay2 Color="blue" />
        <ListDisplay2 Color="red" />
    </ListItems3>
</CascadingValue>

<CascadingValue Value="integerData">
    <ListItems3 TExample="int">
        <ListDisplay2 Color="blue" />
        <ListDisplay2 Color="red" />
    </ListItems3>
</CascadingValue>

@code {
    private List<string> stringData = new() { "Item 1", "Item 2" };
    private List<int> integerData = new() { 1, 2 };
}

Quand plusieurs types génériques sont passés en cascade, les valeurs de tous les types génériques de l’ensemble doivent être passées. Dans l’exemple suivant, TItem, TValue et TEdit sont des types génériques GridColumn, mais le composant parent qui place GridColumn ne spécifie pas le type TItem :

<GridColumn TValue="string" TEdit="TextEdit" />

L’exemple précédent génère une erreur de compilation indiquant que le composant GridColumn ne contient pas le paramètre de type TItem. Le code valide spécifie tous les types :

<GridColumn TValue="string" TEdit="TextEdit" TItem="User" />

Déduire les types génériques en fonction de composants ancêtres

La démonstration dans cette section passe en cascade un type déduit pour TExample.

Remarque

Cette section utilise le composant ListDisplay de la section Prise en charge des types génériques en cascade.

ListItems4.razor:

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Items 4</h2>

@ChildContent

@if (ExampleList is not null)
{
    <ul style="color:green">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>

    <p>
        Type of <code>TExample</code>: @typeof(TExample)
    </p>
}

@code {
    [Parameter]
    public IEnumerable<TExample>? ExampleList { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

Le composant suivant avec des types en cascade déduits fournit des données différentes à des fins d’affichage.

Generics4.razor :

@page "/generics-4"

<PageTitle>Generics 4</PageTitle>

<h1>Generic Type Example 4</h1>

<ListItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListItems4>

<ListItems4 ExampleList="@(new List<int> { 5, 6 })">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<int> { 1, 2 })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<int> { 3, 4 })" />
</ListItems4>

Le composant suivant avec des types en cascade déduits fournit les mêmes données à des fins d’affichage. L’exemple suivant affecte directement les données aux composants.

Generics5.razor :

@page "/generics-5"

<PageTitle>Generics 5</PageTitle>

<h1>Generic Type Example 5</h1>

<ListItems4 ExampleList="stringData">
    <ListDisplay1 Color="blue" ExampleList="stringData" />
    <ListDisplay1 Color="red" ExampleList="stringData" />
</ListItems4>

<ListItems4 ExampleList="integerData">
    <ListDisplay1 Color="blue" ExampleList="integerData" />
    <ListDisplay1 Color="red" ExampleList="integerData" />
</ListItems4>

@code {
    private List<string> stringData = new() { "Item 1", "Item 2" };
    private List<int> integerData = new() { 1, 2 };
}