The Razor @typeparam directive declares a generic type parameter for a Blazor component. Use it when a component should work with different data types without giving up typed parameters. A table template, for example, can accept both the rows and the markup used to display each row. section 34.3.2
In TableTemplate.razor, @typeparam RowType makes RowType available throughout the component. RowData is an IEnumerable<RowType>; RowTemplate is a RenderFragment<RowType>. The component loops over RowData and invokes RowTemplate(item) for each table row. A caller supplies the concrete type and a matching row template. section 34.3.2
Listing 34.19 Adding a type parameter in the Blazor/TableTemplate.razor file
@typeparam RowType
<table class="table table-sm table-bordered table-striped">
@if (Header != null) {
<thead>@Header</thead>
}
<tbody>
@if (RowData != null && RowTemplate != null) {
@foreach (RowType item in RowData) {
<tr>@RowTemplate(item)</tr>
}
}
</tbody>
</table>
@code {
[Parameter]
public RenderFragment? Header { get; set; }
[Parameter]
public RenderFragment<RowType>? RowTemplate { get; set; }
[Parameter]
public IEnumerable<RowType>? RowData { get; set; }
}
See the complete table component: the directive, typed parameters, null checks, and per-item template invocation belong together. section 34.3.2
Listing 34.20 Using a generic template component in the Blazor/PeopleList.razor file
<TableTemplate RowType="Person" RowData="People">
<Header>
<tr><th>ID</th><th>Name</th><th>Dept</th><th>Location</th></tr>
</Header>
<RowTemplate Context="p">
<td>@p.PersonId</td>
<td>@p.Surname, @p.Firstname</td>
<td>@p.Department?.Name</td>
<td>@p.Location?.City, @p.Location?.State</td>
</RowTemplate>
</TableTemplate>
@code {
[Inject]
public DataContext? Context { get; set; }
public IEnumerable<Person>? People => Context?.People
.Include(p => p.Department)
.Include(p => p.Location);
}
See PeopleList.razor pass Person as RowType, provide People as RowData, and define the cells through RowTemplate. section 34.3.2
The same pattern applies beyond display templates. A custom select component declares @typeparam TValue and inherits InputBase<TValue>, so its options and parser can use the model property's value type. Generic type parameters can also use C# where constraints when a component needs types with particular characteristics. section 36.2.1 section 34.3.2
Listing 36.8 The contents of the CustomSelect.razor file in the Blazor/Forms folder
@typeparam TValue
@inherits InputBase<TValue>
@using System.Diagnostics.CodeAnalysis
<select class="form-control @CssClass" value="@CurrentValueAsString"
@onchange="@(ev => CurrentValueAsString = ev.Value as string)">
@ChildContent
@foreach (KeyValuePair<string, TValue> kvp in Values) {
<option value="@kvp.Value">@kvp.Key</option>
}
</select>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public IDictionary<string, TValue> Values { get; set; }
= new Dictionary<string, TValue>();
[Parameter]
public Func<string, TValue>? Parser { get; set; }
protected override bool TryParseValueFromString(string? value,
[MaybeNullWhen(false)] out TValue? result,
[NotNullWhen(false)] out string? validationErrorMessage) {
try {
if (Parser != null && value != null) {
result = Parser(value);
validationErrorMessage = null;
return true;
}
result = default(TValue);
validationErrorMessage = "Value or parser not defined";
return false;
} catch {
result = default(TValue);
validationErrorMessage = "The value is not valid";
return false;
}
}
}
See how CustomSelect.razor carries TValue through its base class, option values, and parser. section 36.2.1
