Header and Footer Layouts with Handlebars.NET

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!