Compatibilidad con tipos genéricos de componentes Razor de ASP.NET Core

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

En este artículo se describe la compatibilidad con tipos genéricos en los componentes Razor.

Si no estás familiarizado con los tipos genéricos, consulta Clases y métodos genéricos (Guía de C#) para obtener instrucciones generales sobre el uso de genéricos antes de leer este artículo.

El código de ejemplo de este artículo solo está disponible para la versión más reciente de .NET en las aplicaciones de ejemplo de Blazor.

Compatibilidad con parámetro de tipo genérico

La directiva @typeparam declara un parámetro de tipo genérico para la clase de componente generada:

@typeparam TItem

Se admite la sintaxis de C# con restricciones de tipo where:

@typeparam TEntity where TEntity : IEntity

En el ejemplo siguiente, el componente ListItems1 se escribe genéricamente como TExample, que representa el tipo de la colección 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; }
}

El siguiente componente representa dos componentes de ListItems1:

  • Los datos de cadenas o enteros se asignan al parámetro ExampleList de cada componente.
  • El tipo string o int que coincide con el tipo de datos asignados se establece para el 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 obtener más información, consulte la referencia sobre la sintaxis de Razor para ASP.NET Core. Para ver un ejemplo de escritura genérica con componentes con plantilla vea Componentes con plantilla Blazor de ASP.NET Core.

Compatibilidad con tipos genéricos en cascada

Un componente antecesor puede organizar en cascada un parámetro de tipo por nombre hasta los descendientes mediante el atributo [CascadingTypeParameter]. Este atributo permite que una inferencia de tipos genérico use el parámetro de tipo especificado de forma automática con los descendientes que tengan un parámetro de tipo con el mismo nombre.

Al agregar @attribute [CascadingTypeParameter(...)] a un componente, el argumento de tipo genérico especificado se usa automáticamente en los descendientes que:

  • Están anidados como contenido secundario del componente en el mismo documento .razor.
  • Declaren también un parámetro @typeparam exactamente con el mismo nombre.
  • No tienen otro valor suministrado explícitamente o inferido implícitamente para el parámetro de tipo. Si se suministra o infiere otro valor, este tiene prioridad sobre el tipo genérico en cascada.

Al recibir un parámetro de tipo en cascada, los componentes obtienen el valor del parámetro del antecesor más cercano que tenga un atributo [CascadingTypeParameter] con un nombre coincidente. Los parámetros de tipo genérico en cascada se invalidan en un subárbol determinado.

La búsqueda de coincidencias solo se realiza por nombre. Por lo tanto, se recomienda evitar un parámetro de tipo genérico en cascada con un nombre genérico, por ejemplo, T o TItem. Si un desarrollador opta por organizar en cascada un parámetro de tipo, promete implícitamente que su nombre es lo suficientemente exclusivo como para no entrar en conflicto con otros parámetros de tipo en cascada de componentes no relacionados.

Los tipos genéricos se pueden conectar en cascada a los componentes secundarios con cualquiera de los enfoques siguientes para componentes antecesores (primarios), que se muestran en las dos subsecciones siguientes:

  • Establezca explícitamente el tipo genérico en cascada.
  • Infiera el tipo genérico en cascada.

En las subsecciones siguientes se proporcionan ejemplos de los enfoques anteriores mediante el componente ListDisplay1 siguiente. El componente recibe y representa los datos de lista que se escriben genéricamente como TExample. Para que cada instancia de ListDisplay1 destaque, un parámetro de componente adicional controla el color de la 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; }
}

Explicitación de tipos genéricos en función de los componentes antecesores

La demostración de esta sección incluye en cascada un tipo explícitamente para TExample.

Nota:

En esta sección se usa el componente ListDisplay1 anterior de la sección Compatibilidad con tipos genéricos en cascada.

El siguiente componente ListItems2 recibe datos y organiza en cascada un parámetro de tipo genérico denominado TExample hasta sus componentes descendientes. En el próximo componente primario, el componente ListItems2 se usa para mostrar datos de lista con el 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; }
}

El siguiente componente primario establece el contenido secundario (RenderFragment) de dos componentes ListItems2 especificando los tipos ListItems2 (TExample), que se conectan en cascada a los componentes secundarios. Los componentes ListDisplay1 se representan con los datos de elemento de lista que se muestran en el ejemplo. Los datos de cadena se usan con el primer componente ListItems2 y los datos enteros se usan con el 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 el tipo de forma explícita también permite usar valores y parámetros en cascada para proporcionar datos a los componentes secundarios, como se indica en la siguiente demostración.

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

Al incluir los datos en cascada en el ejemplo siguiente, el tipo debe proporcionarse al 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 };
}

Cuando hay varios tipos genéricos en cascada, se deben pasar valores para todos los tipos genéricos del conjunto. En el ejemplo siguiente, TItem, TValue y TEdit son tipos genéricos de GridColumn, pero el componente primario que coloca GridColumn no especifica el tipo TItem:

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

En el ejemplo anterior se genera un error en tiempo de compilación en el que el componente GridColumn no tiene el parámetro de tipo TItem. El código válido especifica todos los tipos:

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

Inferencia de tipos genéricos en función de los componentes antecesores

La demostración de esta sección incluye en cascada un tipo inferido para TExample.

Nota:

En esta sección se usa el componente ListDisplay de la sección Compatibilidad con tipos genéricos en cascada.

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

El componente siguiente con tipos en cascada inferidos proporciona datos diferentes para su presentación.

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>

El componente siguiente con tipos en cascada inferidos proporciona los mismos datos para su presentación. En el ejemplo siguiente se asignan directamente los datos a los 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 };
}