liveBook · Manning

@typeparam directive

As explained in Pro ASP.NET Core 7, Tenth Edition

The Razor @typeparam directive declares a generic type parameter for a Razor component.

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

How Pro ASP.NET Core 7, Tenth Edition covers it

How deep it goes
Works through typed component parameters, row rendering, and a generic form input; points to external documentation for constraint details.
Examples
Shows a table template used with Person and Department data, then a custom select component with typed options and parsing.
What it assumes
Assumes familiarity with Razor components and C# properties while introducing the generic parameter through a concrete component.
What you can do after
Build it from scratch: declare a generic Razor component and carry its type parameter through data, templates, or form values.
How it connects
Connects generic type parameters to RenderFragment<T>, IEnumerable<T>, C# where constraints, and InputBase<T>.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Pro ASP.NET Core 7, Tenth Edition. The section links point to the parts of the book it draws on.