Compatibilidade para tipos genéricos de componentes Razor no ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Este artigo descreve o suporte a tipos genéricos em componentes Razor.

Se você não estiver familiarizado com tipos genéricos, consulte Classes e métodos genéricos (Guia do C#) para obter diretrizes gerais sobre o uso de genéricos antes de ler este artigo.

O código de exemplo neste artigo só está disponível para a versão mais recente do .NET nos aplicativos de exemplo Blazor.

Suporte para parâmetro de tipo genérico

A diretiva @typeparam declara um parâmetro de tipo genérico para a classe de componente gerada:

@typeparam TItem

Há suporte para a sintaxe C# com restrições de tipo where:

@typeparam TEntity where TEntity : IEntity

No exemplo a seguir, o componente ListItems1 é digitado genericamente como TExample, que representa o tipo da coleção 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; }
}

O seguinte componente renderiza dois componentes ListItems1:

  • Dados de cadeia de caracteres ou inteiros são atribuídos ao parâmetro ExampleList de cada componente.
  • O tipo string ou int que corresponde ao tipo de dados atribuído é definido para o parâmetro de tipo (TExample) de cada componente.

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

Para obter mais informações, consulte a Referência da sintaxe Razor para ASP.NET Core. Para ver um exemplo de tipo genérico com componentes com modelo, consulte Componentes com modelo Blazor no ASP.NET Core.

Suporte para tipo genérico em cascata

Um componente ancestral pode colocar em cascata, por nome, para os descendentes um parâmetro de tipo usando o atributo [CascadingTypeParameter]. Esse atributo permite que uma inferência de tipo genérico use o parâmetro de tipo especificado automaticamente com descendentes que têm um parâmetro de tipo com o mesmo nome.

Ao adicionar @attribute [CascadingTypeParameter(...)] a um componente, o argumento de tipo genérico especificado é usado automaticamente por descendentes que:

  • São aninhados como conteúdo filho para o componente no mesmo documento .razor.
  • Também declaram um @typeparam com o mesmo nome.
  • Não têm outro valor fornecido explicitamente ou inferido implicitamente para o parâmetro de tipo. Se outro valor for inferido ou fornecido, ele terá precedência sobre o tipo genérico em cascata.

Ao receber um parâmetro de tipo em cascata, os componentes obtêm o valor do parâmetro do ancestral mais próximo que tem um atributo [CascadingTypeParameter] com um nome correspondente. Parâmetros de tipo genérico em cascata são substituídos em uma subárvore específica.

A correspondência é realizada apenas pelo nome. Portanto, recomendamos evitar um parâmetro de tipo genérico em cascata com um nome genérico, por exemplo, T ou TItem. Se um desenvolvedor optar por colocar em cascata um parâmetro de tipo, ele estará implicitamente prometendo que o nome é exclusivo o suficiente para não entrar em conflito com outros parâmetros de tipo em cascata de componentes não relacionados.

Os tipos genéricos podem ser colocados em cascata para componentes filho com qualquer uma das seguintes abordagens para componentes ancestrais (pai), que são demonstrados nas seguintes duas subseções:

  • Definir explicitamente o tipo genérico em cascata.
  • Inferir o tipo genérico em cascata.

As subseções a seguir fornecem exemplos das abordagens anteriores usando o componente ListDisplay1 a seguir. O componente recebe e renderiza dados de lista tipado genericamente como TExample. Para destacar cada instância de ListDisplay1, um parâmetro de componente adicional controla a cor da lista.

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

Tipos genéricos explícitos baseados em componentes ancestrais

A demonstração nesta seção coloca um tipo em cascada explicitamente para TExample.

Observação

Esta seção usa o componente ListDisplay1 anterior na seção Suporte para tipo genérico em cascata.

O componente ListItems2 a seguir recebe dados e coloca em cascata um parâmetro de tipo genérico chamado TExample para os componentes descendentes. No próximo componente pai, o componente ListItems2 é usado para exibir dados de lista com o componente ListDisplay1 anterior.

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

O componente pai a seguir define o conteúdo filho (RenderFragment) de dois componentes ListItems2 que especificam os tipos ListItems2 (TExample), dos quais se derivam componentes filho em cascata. Os componentes ListDisplay1 são renderizados com os dados do item de lista mostrados no exemplo. Os dados de cadeia de caracteres são usados com o primeiro componente ListItems2, e os dados inteiros são usados com o segundo componente 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>

Especificar o tipo explicitamente também permite o uso de valores e parâmetros em cascata para fornecer dados a componentes filho, como mostra a demonstração a seguir.

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

Ao colocar em cascata os dados no exemplo a seguir, o tipo precisa ser fornecido ao componente .

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

Quando vários tipos genéricos são colocados em cascata, os valores para todos eles no conjunto devem ser passados. No exemplo a seguir, TItem, TValue e TEdit são tipos genéricos GridColumn, mas o componente pai que coloca GridColumn não especifica o tipo TItem:

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

O exemplo anterior gera um erro em tempo de compilação em que o componente GridColumn está sem o parâmetro de tipo TItem. O código válido especifica todos os tipos:

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

Inferir tipos genéricos com base em componentes ancestrais

A demonstração nesta seção coloca um tipo em cascata explicitamente para TExample.

Observação

Esta seção usa o componente ListDisplay na seção Suporte para tipo genérico em cascata.

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

O componente a seguir com tipos em cascata inferidos fornece dados diferentes para exibição.

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>

O componente a seguir com tipos em cascata inferidos fornece os mesmos dados para exibição. O exemplo a seguir atribui diretamente os dados aos componentes.

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