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 7 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 7 version of this article.

This article describes generic type support in Razor components.

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 ListGenericTypeItems1 component is generically typed as TExample.

ListGenericTypeItems1.razor:

@typeparam TExample

@if (ExampleList is not null)
{
    <ul>
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

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

@if (ExampleList is not null)
{
    <ul>
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

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

@if (ExampleList is not null)
{
    <ul>
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

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

@if (ExampleList != null)
{
    <ul>
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

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

The following GenericTypeExample1 component renders two ListGenericTypeItems1 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.

GenericTypeExample1.razor:

@page "/generic-type-example-1"

<h1>Generic Type Example 1</h1>

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

<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })" 
                       TExample="int" />
@page "/generic-type-example-1"

<h1>Generic Type Example 1</h1>

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

<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })" 
                       TExample="int" />
@page "/generic-type-example-1"

<h1>Generic Type Example 1</h1>

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

<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })" 
                       TExample="int" />
@page "/generic-type-example-1"

<h1>Generic Type Example 1</h1>

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

<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })" 
                       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 CascadingTypeParameterAttribute 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 in either of the following approaches with 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 two ListDisplay components. The components receive and render list data and are generically typed as TExample. These components are for demonstration purposes and only differ in the color of text that the list is rendered. If you wish to experiment with the components in the following sub-sections in a local test app, add the following two components to the app first.

ListDisplay1.razor:

@typeparam TExample

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

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

ListDisplay2.razor:

@typeparam TExample

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

@code {
    [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 two ListDisplay components in the Cascaded generic type support section.

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

ListGenericTypeItems2.razor:

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

<h2>List Generic Type Items 2</h2>

@ChildContent

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

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

GenericTypeExample2.razor:

@page "/generic-type-example-2"

<h1>Generic Type Example 2</h1>

<ListGenericTypeItems2 TExample="string">
    <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
    <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListGenericTypeItems2>

<ListGenericTypeItems2 TExample="int">
    <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
    <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems2>

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

ListDisplay3.razor:

@typeparam TExample

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

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

ListDisplay4.razor:

@typeparam TExample

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

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

ListGenericTypeItems3.razor:

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

<h2>List Generic Type 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 ListGenericTypeItems3 component.

GenericTypeExample3.razor:

@page "/generic-type-example-3"

<h1>Generic Type Example 3</h1>

<CascadingValue Value="@stringData">
    <ListGenericTypeItems3 TExample="string">
        <ListDisplay3 />
        <ListDisplay4 />
    </ListGenericTypeItems3>
</CascadingValue>

<CascadingValue Value="@integerData">
    <ListGenericTypeItems3 TExample="int">
        <ListDisplay3 />
        <ListDisplay4 />
    </ListGenericTypeItems3>
</CascadingValue>

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

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 two ListDisplay components in the Cascaded generic type support section.

ListGenericTypeItems4.razor:

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

<h2>List Generic Type 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 GenericTypeExample4 component with inferred cascaded types provides different data for display.

GenericTypeExample4.razor:

@page "/generic-type-example-4"

<h1>Generic Type Example 4</h1>

<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })">
    <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
    <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListGenericTypeItems4>

<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })">
    <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
    <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems4>

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

GenericTypeExample5.razor:

@page "/generic-type-example-5"

<h1>Generic Type Example 5</h1>

<ListGenericTypeItems4 ExampleList="@stringData">
    <ListDisplay1 ExampleList="@stringData" />
    <ListDisplay2 ExampleList="@stringData" />
</ListGenericTypeItems4>

<ListGenericTypeItems4 ExampleList="@integerData">
    <ListDisplay1 ExampleList="@integerData" />
    <ListDisplay2 ExampleList="@integerData" />
</ListGenericTypeItems4>

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