ASP.NET Core Razor component generic type support

Note

This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 8 version of this article.

This article describes generic type support in Razor components.

If you're new to generic types, see Generic classes and methods (C# Guide) for general guidance on the use of generics before reading this article.

The example code in this article is only available for the latest .NET release in the Blazor sample apps.

Generic type parameter support

The @typeparam directive declares a generic type parameter for the generated component class:

@typeparam TItem

C# syntax with where type constraints is supported:

@typeparam TEntity where TEntity : IEntity

In the following example, the ListItems1 component is generically typed as TExample, which represents the type of the ExampleList collection.

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

The following component renders two ListItems1 components:

  • String or integer data is assigned to the ExampleList parameter of each component.
  • Type string or int that matches the type of the assigned data is set for the type parameter (TExample) of each component.

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

For more information, see Razor syntax reference for ASP.NET Core. For an example of generic typing with templated components, see ASP.NET Core Blazor templated components.

Cascaded generic type support

An ancestor component can cascade a type parameter by name to descendants using the [CascadingTypeParameter] attribute. This attribute allows a generic type inference to use the specified type parameter automatically with descendants that have a type parameter with the same name.

By adding @attribute [CascadingTypeParameter(...)] to a component, the specified generic type argument is automatically used by descendants that:

  • Are nested as child content for the component in the same .razor document.
  • Also declare a @typeparam with the exact same name.
  • Don't have another value explicitly supplied or implicitly inferred for the type parameter. If another value is supplied or inferred, it takes precedence over the cascaded generic type.

When receiving a cascaded type parameter, components obtain the parameter value from the closest ancestor that has a [CascadingTypeParameter] attribute with a matching name. Cascaded generic type parameters are overridden within a particular subtree.

Matching is only performed by name. Therefore, we recommend avoiding a cascaded generic type parameter with a generic name, for example T or TItem. If a developer opts into cascading a type parameter, they're implicitly promising that its name is unique enough not to clash with other cascaded type parameters from unrelated components.

Generic types can be cascaded to child components with either of the following approaches for ancestor (parent) components, which are demonstrated in the following two sub-sections:

  • Explicitly set the cascaded generic type.
  • Infer the cascaded generic type.

The following subsections provide examples of the preceding approaches using the following ListDisplay1 component. The component receives and renders list data generically typed as TExample. To make each instance of ListDisplay1 stand out, an additional component parameter controls the color of the list.

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

Explicit generic types based on ancestor components

The demonstration in this section cascades a type explicitly for TExample.

Note

This section uses the preceding ListDisplay1 component in the Cascaded generic type support section.

The following ListItems2 component receives data and cascades a generic type parameter named TExample to its descendent components. In the upcoming parent component, the ListItems2 component is used to display list data with the preceding ListDisplay1 component.

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

The following parent component sets the child content (RenderFragment) of two ListItems2 components specifying the ListItems2 types (TExample), which are cascaded to child components. ListDisplay1 components are rendered with the list item data shown in the example. String data is used with the first ListItems2 component, and integer data is used with the second ListItems2 component.

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>

Specifying the type explicitly also allows the use of cascading values and parameters to provide data to child components, as the following demonstration shows.

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

When cascading the data in the following example, the type must be provided to the component.

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

When multiple generic types are cascaded, values for all generic types in the set must be passed. In the following example, TItem, TValue, and TEdit are GridColumn generic types, but the parent component that places GridColumn doesn't specify the TItem type:

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

The preceding example generates a compile-time error that the GridColumn component is missing the TItem type parameter. Valid code specifies all of the types:

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

Infer generic types based on ancestor components

The demonstration in this section cascades a type inferred for TExample.

Note

This section uses the ListDisplay component in the Cascaded generic type support section.

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

The following component with inferred cascaded types provides different data for display.

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>

The following component with inferred cascaded types provides the same data for display. The following example directly assigns the data to the components.

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