Header and Footer Layouts with Handlebars.NET
One of the last blog posts (Text and EMail Templates with Handlebars.NET) was generally about HandleBars.NET - a very popular template engine. This post is about how you can easily implement layouts with Handlebars.
The snippet in the previous post was as follows:
// MyTemplateProvider.cs
public MyTemplateProvider()
{
_handlebars = Handlebars.Create();
Init(); // Add this
}
private void Init()
{
// Add Header Template
_handlebars.RegisterTemplate("Header_Template", @"<strong>MyCompany - {{Title}}</strong>");
// Add Footer Template
_handlebars.RegisterTemplate("Footer_Template", @"<i>MyCompany - Copyright 2023</i>");
// Register Welcome Mail
_handlebars.RegisterTemplate("WelcomeMail_Template",
"""
<div>
<h1>{{>Header_Template}}</h1>
<div class="body">
<h3>Welcome to our demo {{UserName}}!</h3>
<p>Thank you for registering. Have fun!</p>
</div>
<footer>{{>Footer_Template}}</footer>
</div>
""");
}
As you can see, a template is registered that references other templates - but explicitly. I therefore have to take care of the frame - in this case the header and footer - for my actual content myself. This is not nice and represents a redundancy.
Partial Blocks
Handlebars supports a functionality called Partial Blocks
. While simple Partials
represent a content block, partial blocks are the construct around it. In Razor, this is comparable to sections. Partial blocks can therefore be used to represent the surrounding content - like our layouts.
_handlebars.RegisterTemplate("MailLayout", """
<div>
<h1><strong>MyCompany - {{Title}}</strong></h1>
<div class="body">
{{> content}}
</div>
<footer><i>MyCompany - Copyright {{On.Year}}</i></footer>
</div>
""");
Partial blocks are also registered as templates, but have the special feature that they contain the reserved partial keyword content
. This is then replaced by the actual content you have in your template.
The layout can then be referenced in the actual template by specifying the partial block:
_handlebars.RegisterTemplate("WelcomeMail_Template",
"""{{#> MailLayout}}
<h3>Welcome to our demo {{UserName}}!</h3>
<p>Thank you for registering. Have fun!</p>
{{/MailLayout}}
""");
Full Sample
using HandlebarsDotNet;
namespace BenjaminAbt.Samples.HandlebarsNET;
internal class Program
{
static void Main(string[] args)
{
MyTemplateProvider templateProvider = new();
List<string> userNames = ["Batman", "Robin"];
foreach (var userName in userNames)
{
string text = templateProvider.RenderWelcomeMail(userName);
Console.WriteLine($"To: {userName}");
Console.WriteLine(text);
Console.WriteLine("-------------------");
}
}
}
// MyTemplateProvider.cs
public class MyTemplateProvider
{
private readonly IHandlebars _handlebars;
public MyTemplateProvider()
{
_handlebars = Handlebars.Create();
Init(); // Add this
}
private void Init()
{
_handlebars.RegisterTemplate("MailLayout", """
<div>
<h1><strong>MyCompany - {{Title}}</strong></h1>
<div class="body">
{{> content}}
</div>
<footer><i>MyCompany - Copyright {{On.Year}}</i></footer>
</div>
""");
// Register Welcome Mail
_handlebars.RegisterTemplate("WelcomeMail_Template", InternalRegisterTemplateWithLayout(
"""
<h3>Welcome to our demo {{UserName}}!</h3>
<p>Thank you for registering. Have fun!</p>
"""));
}
private string InternalRegisterTemplateWithLayout(string template)
=> "{{#> MailLayout}}" + template + "{{/MailLayout}}";
private HandlebarsTemplate<object, object>? _welcomeTemplate = null;
public string RenderWelcomeMail(string userName)
{
_welcomeTemplate ??= _handlebars.Compile("{{> WelcomeMail_Template}}");
var data = new
{
Title = "Welcome!",
UserName = userName,
On = DateTimeOffset.UtcNow
};
var text = _welcomeTemplate(data);
return text;
}
}
Have fun!