4/19/2020 Admin

Advanced Blazor Templating


image

Basic Blazor templating has been covered, but what if you need to make a Blazor template that takes another template as a parameter?

image

SQL-MisterMagoo recently posted a sample that demonstrates Blazor templating. In this post we will examine what it does.

We will first look at the basic examples that use a simple collection of DateTime objects to demonstrate how templating, and the various options work.

We will conclude by looking at how the navigation menu, in the sample code, is dynamically rendered using the techniques.

Note: The GitHub sample code is constantly being updated, so this article is based on a Blazor Server Side version of the 4/19/2020 code posted at this link.

image

SQL-MisterMagoo offers his opinion on what he feels a person would gain from examining this code.

 

Blazor Templated Components

A templated component is a Blazor component that has one or more parameters of type RenderFragment (or the generic RenderFragment<T>).

A RenderFragment parameter accepts User Interface (UI) that is then rendered by the component. This allows you to fully customize the look and feel of the resulting component while still reusing much of the component structure and logic.

Templates can also be generic, meaning the template can be used for collections of different types (in this article we will demonstrate using a collection of DateTime, and a collection of MenuData (composed of the properties RelativeUrl and MenuTitle).

The Microsoft Blazor Documentation site provides an example, using @typeparam and RenderFragment<T>, to create a generic template.

 

A Magoo

image

As we examine the code we will see an unusual character sequence:

 

	@: @{ 
		...
	}

 

In the article Passing placeholders to RenderFragments Peter Morris, describes the character sequence this way:

image

 

Or as Linden Mikus says…

image

In this example code we will see the Magoo used in code like this:

 

	RepeaterTemplate = context =>
	@: @{ 
		<div class="card-columns">@context</div>
	}
         /**/;

 

This code creates a RenderFragment called RepeaterTemplate that is composed of a Div that contains HTML and C# code.

Note: the /**/ character sequence is required for the code to compile in Visual Studio.

 

The Repeater Control

image

All the examples use the templated component, called Repeater, that is contained in the Repeater.razor file.

 

image

The simplest implementation is contained in the Index.razor page, that contains the following code that simply builds a collection of three DateTime objects, and passes them to the Repeater control through its Data property:

 

<Repeater Data=@Data />
@code {
    DateTime[] Data =
    Enumerable.Range(1, 3)
    .Select(i => DateTime.Now.AddDays(i))
    .ToArray();
}

 

This renders the following:

 

image

The repeater control takes the following parameters:

 

	/****** DECLARE PARAMETERS ******/
	[Parameter] public IEnumerable<T> Data { get; set; }
	[Parameter] public RenderFragment<RenderFragment> RepeaterTemplate { get; set; }
	[Parameter] public RenderFragment<RenderFragment> RepeaterItemTemplate { get; set; }
	[Parameter] public RenderFragment<T> RepeaterField
	{
		get=>_Contents.LastOrDefault();
		set
		{
			_Contents.Add(value);
		}
	}

 

However, in this case, only the Data parameter is passed.

The Repeater control needs the remaining RenderFragments defined, so it contains code like this, using Magoo syntax, to create default RenderFragments when they are not defined:

image

The following diagram shows how the values are mapped to the templates and the templates are mapped to the page:

 

image

 

Passing A Template as a Parameter

Basic Blazor templating allows you to pass in your own properties and perhaps enable or disable basic UI. However, advanced Blazor templating allows you to define a template as a RenderFragment, that contains HTML and C# syntax.

SQL-MisterMagoo has constructed the Repeater control to allow you to pass it a Data collection, and to define the RenderFragments: RepeaterTemplate, RepeaterItemTemplate, and the RepeaterField, to produce a wide range of output as demonstrated in the following examples:

 

image

 

<Repeater Data=@Data>
	<RepeaterTemplate Context="ListItem">
		<div class="row">@ListItem</div>
	</RepeaterTemplate>
	<RepeaterItemTemplate Context="ListFields">
		<div class="col m-1">@ListFields</div>
	</RepeaterItemTemplate>
</Repeater>

 

image

 

<Repeater Data=@Data>
	<RepeaterTemplate Context="ListItem">
		<div class="card-columns">@ListItem</div>
	</RepeaterTemplate>
	<RepeaterItemTemplate Context="ListFields">
		<div class="card border-dark mb-3" style="max-width: 18rem;">
        <div class="card-body">@ListFields</div></div>
	</RepeaterItemTemplate>
	<RepeaterField Context="DataType">
		<div class="form-group row">
			<label class="col-form-label">@nameof(DataType)</label>
			<div class="form-control" readonly>@DataType.GetType()</div>
		</div>
	</RepeaterField>
	<RepeaterField Context="Data">
		<div class="form-group row">
			<label class="col-form-label">@nameof(Data)</label>
			<div class="form-control" readonly>@Data</div>
		</div>
	</RepeaterField>
</Repeater>

 

image

 

<Repeater Data=@Data>
	<RepeaterTemplate Context="ListItems">
		<table class="table-bordered">
			<tr><th>Data Type</th><th>Value</th></tr>
			@ListItems
			<tr><th colspan="2">Using the Repeater as a table.</th></tr>
		</table>
	</RepeaterTemplate>
	<RepeaterItemTemplate Context="ListFields">
		<tr>@ListFields</tr>
	</RepeaterItemTemplate>
	<RepeaterField Context="DataType">
			<td>@DataType.GetType()</td>
	</RepeaterField>
	<RepeaterField Context="Data">
			<td>@Data</td>
	</RepeaterField>
</Repeater>

 

 

The Navigation Page

image

The best example of the utility of using a control such as the Repeater control, is demonstrated in the Navigation component of the application.

At runtime, code determines what pages are in the application, and creates a collection of MenuData items:

 

_Menu = new List<MenuData>();
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
foreach (var type in 
	assembly
		.GetExportedTypes()
		.Where(type => type.CustomAttributes.Any(
			attr => attr.AttributeType.Equals(typeof(RouteAttribute))))
		)
{
	var urlAttr = 
		type
			.CustomAttributes
			.Where(at => at.AttributeType.Equals(typeof(RouteAttribute)))
			.First();
	var titleAttr = 
		type
			.CustomAttributes
			.Where(at => at.AttributeType.Equals(typeof(PageTitleAttribute)))
			.FirstOrDefault();
					
	var url = urlAttr.ConstructorArguments.First().Value.ToString().TrimStart('/');
	var title = titleAttr?.ConstructorArguments.First().Value.ToString() ?? type.Name;
	_Menu.Add(new MenuData { RelativeUrl = url, MenuTitle = title });
					
}
_Menu = _Menu.OrderBy(md => md.RelativeUrl).ToList<MenuData>();

 

The MenuData collection is passed to the Repeater control, through the Data property, to be formatted as a fully operational navigation menu, using the following markup:

 

<Repeater @key="this" Data=Menu>
	<RepeaterTemplate>
		<ul class="nav flex-column">
			@context
		</ul>
	</RepeaterTemplate>
	<RepeaterItemTemplate>
		@context
	</RepeaterItemTemplate>
	<RepeaterField>
		<li class="nav-item px-3">
			@if (string.IsNullOrWhiteSpace(context.RelativeUrl))
			{
			<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
				<span class="oi oi-home" aria-hidden="true"></span> 
				@context.MenuTitle
			</NavLink>
			}
			else
			{
			<NavLink class="nav-link" href=@context.RelativeUrl>
				<span class="oi oi-list-rich" aria-hidden="true"></span> 
				@context.MenuTitle
			</NavLink>
			}
		</li>
	</RepeaterField>
</Repeater>

 

Links

Mistermag00 Twitter

SQL-MisterMagoo GitHub (Server Side version used in this article)

ASP.NET Core Blazor templated components

Passing placeholders to RenderFragments

Blazor.net

An error has occurred. This application may no longer respond until reloaded. Reload 🗙