Sometimes we need to create components that mix consumer-supplied mark-up with their own rendered output.
It would be very messy to pass content to a component as an HTML encoded string parameter:
<Collapsible content="Lots of encoded HTML for your entire view here"/>
And, in addition to the maintenance nightmare, the embedded HTML could only be basic HTML mark-up too, no Blazor components. Basically, it’d be useless, and obviously that’s not how it should be done. The correct approach is to use a RenderFragment
.
Without RenderFragment
Index.razor
<table class="table">
<tr>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
</tr>
<tr>
<td>John</td>
<td>Male</td>
<td>37</td>
</tr>
<tr>
<td>Rose</td>
<td>Female</td>
<td>32</td>
</tr>
<tr>
<td>Martin</td>
<td>Male</td>
<td>1</td>
</tr>
</table>
Child Content
These are the criteria Blazor uses to inject embedded content into a component. The embedded content may be anything you wish; plain text, HTML elements, more razor mark-up (including more components), and the content of that embedded content may be output anywhere in your component’s mark-up simply by adding @ChildContent
.
TableTemplate.razor
<table class="table">
<tr>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
</tr>
@ChildContent
</table>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
Index.razor
@page "/"
<TableTemplate>
<tr>
<td>John</td>
<td>Male</td>
<td>37</td>
</tr>
<tr>
<td>Rose</td>
<td>Female</td>
<td>32</td>
</tr>
<tr>
<td>Martin</td>
<td>Male</td>
<td>1</td>
</tr>
</TableTemplate>
Multiple RenderFragments
When we write mark-up inside a component, Blazor will assume it should be assigned to a Parameter on the component that is descended from the RenderFragment
class and is named ChildContent
. If we wish to use a different name, or multiple render fragments, then we must explicitly specify the parameter’s name in our mark-up.
TableTemplate.razor
<table class="table">
<tr>
@TableHeader
</tr>
@ChildContent
</table>
@code {
[Parameter]
public RenderFragment TableHeader { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
Index.razor
@page "/"
<TableTemplate>
<TableHeader>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
</TableHeader>
<ChildContent>
<tr>
<td>John</td>
<td>Male</td>
<td>37</td>
</tr>
<tr>
<td>Rose</td>
<td>Female</td>
<td>32</td>
</tr>
<tr>
<td>Martin</td>
<td>Male</td>
<td>1</td>
</tr>
</ChildContent>
</TableTemplate>
Passing data to a RenderFragment
As well as the standard RenderFragment
class, there is also a generic RenderFragment<T>
class that can be used to pass data into the RenderFragment
.
TableTemplate.razor
@using System.Diagnostics.CodeAnalysis
@typeparam TItem
<table class="table">
<tr>
@TableHeader
</tr>
@foreach (var item in Items)
{
if (RowTemplate is not null)
{
<tr>@RowTemplate(item)</tr>
}
}
</table>
@code {
[Parameter]
public RenderFragment TableHeader { get; set; }
[Parameter, AllowNull]
public IReadOnlyList<TItem> Items { get; set; }
[Parameter]
public RenderFragment<TItem>? RowTemplate { get; set; }
}
Index.razor
@page "/"
<TableTemplate Items="people" Context="person">
<TableHeader>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
</TableHeader>
<RowTemplate>
<td>@person.Name</td>
<td>@person.Gender</td>
<td>@person.Age</td>
</RowTemplate>
</TableTemplate>
@code
{
private List<Person> people = new()
{
new Person() { Name = "John", Gender = "Male", Age = 37 },
new Person() { Name = "Rose", Gender = "Female", Age = 32 },
new Person() { Name = "Martin", Gender = "Male", Age = 1 },
};
private class Person
{
public string Name { get; set; }
public string Gender { get; set; }
public int Age { get; set; }
}
}
References
https://blazor-university.com/templating-components-with-renderfragements/
https://blazor-university.com/templating-components-with-renderfragements/passing-data-to-a-renderfragement/
https://docs.microsoft.com/en-us/aspnet/core/blazor/components/templated-components?view=aspnetcore-6.0