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
orint
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 };
}
Feedback
Submit and view feedback for