Generating formatted reports based on application data is a very common need. For example, you may want to create an HTML page with content from a receipt. This content may be sent in an HTML formatted email or converted to PDF or any other use case. To achieve this, a flexible and capable templating engine is needed to transform the application data to a human readable format.
.net has a very powerful templating engine that's used in its asp.net web framework which is Razor templates. But what if you want to use a templating engine that is simpler, and doesn't require a web stack as in the case of building background jobs, desktop or mobile applications?
Handlebars.net is a .net implementation of the famous HandlebarsJS templating framework. From Handlebars.net Github repository:
"Handlebars.Net doesn't use a scripting engine to run a Javascript library - it compiles Handlebars templates directly to IL bytecode. It also mimics the JS library's API as closely as possible."For example: consider this collection of data that should be rendered as an HTML table:
var employees = new []
{
new Employee
{
BirthDate= DateTime.Now.AddYears(-20),
Name = "John Smith",
Photo = new Uri("https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Houghton_STC_22790_-_Generall_Historie_of_Virginia%2C_New_England%2C_and_the_Summer_Isles%2C_John_Smith.jpg/800px-Houghton_STC_22790_-_Generall_Historie_of_Virginia%2C_New_England%2C_and_the_Summer_Isles%2C_John_Smith.jpg")
},
new Employee
{
BirthDate= DateTime.Now.AddYears(-25),
Name = "Jack",
Photo = new Uri("https://upload.wikimedia.org/wikipedia/commons/e/ec/Jack_Nicholson_2001.jpg")
},
new Employee
{
BirthDate= DateTime.Now.AddYears(-40),
Name = "Iron Man",
Photo = new Uri("https://upload.wikimedia.org/wikipedia/en/4/47/Iron_Man_%28circa_2018%29.png")
},
};
A Handlebars template may look like:
<html>
<body>
<table border="1">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Photo</th>
</tr>
</thead>
<tbody>
{{#each this}}
<tr>
<td>{{Name}}</td>
<td>{{BirthDate}}</td>
</tr>
{{/each}}
</tbody>
</table>
</body>
</html>
The template is fairly simple. Explaining the syntax of Handlebars templates is beyond the scope of this article. Check Handlebarjs Language Guide for information regarding its syntax.
Passing the data to the Hanledbar.net and render the template is easy:
var template = File.ReadAllText("List.handlebars");
var compiledTemplate = Handlebars.Compile(template);
var output = compiledTemplate(employees);
Console.WriteLine(output);
Line 1 reads the List.handlebars template which is stored in the same application folder, alternatively the template can be stored as an embedded resource or retrieved from a database or even created on the fly.
Line 2 compiles the template, generating a function that can be invoked later.
Note: For good performance, the compiled template should be generated once and used multiple times during the lifetime of the application.
Line 3 invokes the function passing the employees collection and receives the rendered output in a string variable.
This is the generated HTML:
<html>
<body>
<table border="1">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Photo</th>
</tr>
</thead>
<tbody>
<tr>
<td>John Smith</td>
<td>2003-09-09T22:08:23.3541971+10:00</td>
<td><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Houghton_STC_22790_-_Generall_Historie_of_Virginia%2C_New_England%2C_and_the_Summer_Isles%2C_John_Smith.jpg/800px-Houghton_STC_22790_-_Generall_Historie_of_Virginia%2C_New_England%2C_and_the_Summer_Isles%2C_John_Smith.jpg" width="200px" height="200px" /></td>
</tr>
<tr>
<td>Jack</td>
<td>1998-09-09T22:08:23.3839317+10:00</td>
<td><img src="https://upload.wikimedia.org/wikipedia/commons/e/ec/Jack_Nicholson_2001.jpg" width="200px" height="200px" /></td>
</tr>
<tr>
<td>Iron Man</td>
<td>1983-09-09T22:08:23.3839479+10:00</td>
<td><img src="https://upload.wikimedia.org/wikipedia/en/4/47/Iron_Man_%28circa_2018%29.png" width="200px" height="200px" /></td>
</tr>
</tbody>
</table>
</body>
</html>
And this is how the output is rendered by a browser:
Putting aside lack of styling which has nothing to do with Handlebars, the output seems good but suffers for two issues:
- The format of the Age property is not great.
- The image tags rendered by the template reference the full URL of the images. Every time the generated HTML is consumed and rendered, it will have to fetch the images from their sources, which may be inconvenient. Additionally, the generated template is not self-contained, and other services that consume the generated HTML (like an HTML to PDF conversion service) will have to download the images.
Although the Handlebars has a powerful templating language, it's impossible to cover all needs that may arise, this is why Handlebars.net provides the ability to define custom helpers.
Custom Helpers:
Let's use helpers to solve the date format issue:
Handlebars.RegisterHelper("formatDate", (output, context, arguments)
=> { output.Write(((DateTime)arguments[0]).ToString(arguments[1].ToString())); });
<td>{{formatDate BirthDate "dd/MM/yyyy"}}</td>
The rendered output is much better now:
Embedding images in the HTML output
This is a basic implementation of this "embeddedImage" helper:
Handlebars.RegisterHelper("embeddedImage", (output, context, arguments) =>
{
var url = arguments[0] as Uri;
using var httpClient = new HttpClient();
// add user-agent header required by Wikipedia. You should safely ommit the following line for other sources
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("example.com-bot", "1.0"));
var content = httpClient.GetByteArrayAsync(url).Result;
var encodedContent = Convert.ToBase64String(content);
output.Write("data:image/png;base64," + encodedContent);
});
The code uses an HttpClient to download the image as a byte array, then encode it using base64 encoding, then writes the output as a data URI using the standards format. And the usage is very simple:
<img width="200px" height="200px" src="{{embeddedImage Photo}}" />
And the HTML output looks like: (trimmed for brevity)
<img width="200px" height="200px" src="data:image/png;base64,/9j/4gIcSUNDX1BST0ZJTEUAAQEAAAIMbGNtcwIQAABtbnRyUkdCIFhZWiAH3AABABkAAwApAD.....
Conclusion
One of the most important design principals is the Open-Closed Principal: software entities should be open for extension but closed for modification. Handlebars and Handlebars.net apply this principal by allowing users to extend the functionality of the library without having to modify its source code, which is a good design.
With a plethora of free and commercial libraries available for developers, the level of extensibility should be one of the evaluation criteria used during the selection process.
And you, what other templating libraries have you used in .net applications? How extensible are these libraries?
No comments:
Post a Comment