поддержка универсального типа компонента ASP.NET Core Razor

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

В этой статье описывается поддержка универсального типа в Razor компонентах.

Если вы не знакомы с универсальными типами, ознакомьтесь с общими классами и методами (руководство по C#), чтобы ознакомиться с общими рекомендациями по использованию универсальных шаблонов перед чтением этой статьи.

Пример кода в этой статье доступен только для последнего выпуска .NET в Blazor примерах приложений.

Поддержка параметров универсального типа

Директива @typeparam объявляет параметр универсального типа для созданного класса компонента:

@typeparam TItem

Поддерживается синтаксис C# с ограничениями типа where:

@typeparam TEntity where TEntity : IEntity

В следующем примере ListItems1 компонент является универсальным типом TExample, который представляет тип 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; }
}

Следующий компонент отображает два ListItems1 компонента:

  • Параметру ExampleList каждого компонента назначаются строковые или целочисленные данные.
  • Параметру типа (TExample) каждого компонента задается тип string или int, совпадающий с типом назначенных данных.

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

Дополнительные сведения см. в справочнике по синтаксису Razor для ASP.NET Core. Пример универсальной типизации с помощью шаблонных компонентов см. в статье Шаблонные компоненты в ASP.NET CoreBlazor.

Поддержка каскадированного универсального типа

Компонент-предок может каскадировать параметр типа по имени с помощью атрибута [CascadingTypeParameter] на потомки. Этот атрибут позволяет определению универсального типа автоматически использовать указанный параметр типа с потомками, имеющими параметр типа с таким же именем.

Если добавить @attribute [CascadingTypeParameter(...)] к компоненту, указанный аргумент универсального типа будут автоматически использовать потомки, которые:

  • Вложены в качестве дочернего содержимого для компонента в том же документе .razor.
  • Также объявляют @typeparam с тем же именем.
  • Для параметра типа не следует явным образом указывать или неявным образом выводить другое значение. Поскольку, если указать или вывести другое значение, оно будет иметь приоритет над каскадным универсальным типом.

При получении каскадного параметра типа компоненты получают значение параметра от ближайшего предка, имеющего [CascadingTypeParameter] атрибут с соответствующим именем. Каскадные параметры универсального типа переопределяются в определенном поддереве.

А сопоставление выполняется только по имени. Поэтому мы рекомендуем не использовать каскадные параметры универсального типа с универсальными именами, например T или TItem. Выбирая каскадирование параметра типа, разработчик неявно гарантирует, что имя параметра типа является уникальным и не будет конфликтовать с другими каскадными параметрами типа из несвязанных компонентов.

Универсальные типы можно каскадировать для дочерних компонентов с помощью любого из следующих подходов к компонентам предка (родителя), которые демонстрируются в следующих двух подразделах:

  • Явное задание каскадного универсального типа.
  • Вывод каскадного универсального типа.

В следующих подразделах приведены примеры предыдущих подходов с помощью следующего ListDisplay1 компонента. Компонент получает и отрисовывает данные списка, типизированные TExampleкак . Чтобы выделить каждый экземпляр, дополнительный ListDisplay1 параметр компонента управляет цветом списка.

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

Явный вывод универсальных типов на основе компонентов-предков

В демонстрации в этом разделе тип явным образом каскадируется для TExample.

Примечание.

В этом разделе используется предыдущий ListDisplay1 компонент в разделе поддержки каскадного универсального типа.

Следующий компонент ListItems2 получает данные и каскадирует параметр универсального типа с именем TExample на компоненты-потомки. В последующем родительском компоненте компонент ListItems2 используется для вывода списка данных с помощью предыдущего компонента ListDisplay1.

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

Следующий родительский компонент задает дочернее содержимое () двух ListItems2 компонентов, указывающих ListItems2 типы (RenderFragmentTExample), которые каскадируются для дочерних компонентов. Компоненты ListDisplay1отрисовываются с данными элемента списка, показанными в примере. Строковые данные используются с первым компонентом ListItems2, а целочисленные данные — со вторым компонентом 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>

Явное указание типа также позволяет использовать каскадные значения и параметры для предоставления данных дочерним компонентам, как показано в следующем примере.

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

При каскадировании данных в следующем примере для компонента необходимо указать тип.

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

Если каскадируется несколько универсальных типов, необходимо передать значения для всех универсальных типов в наборе. В следующем примере TItem, TValue и TEdit являются универсальными типами GridColumn, но родительский компонент, размещающий GridColumn, не указывает тип TItem:

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

В предыдущем примере создается ошибка времени компиляции, так как в компоненте GridColumn отсутствует параметр типа TItem. Следующий допустимый код указывает все типы:

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

Вывод универсальных типов на основе компонентов-предков

В демонстрации в этом разделе каскадируется тип, выводимый для TExample.

Примечание.

В этом разделе используется ListDisplay компонент в разделе поддержки каскадного универсального типа.

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

Следующий компонент с выводимыми каскадированными типами предоставляет различные данные для отображения.

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>

Следующий компонент с выводимыми каскадированными типами предоставляет одинаковые данные для отображения. В следующем примере данные присваиваются компонентам напрямую.

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