Handlebars.js is an extension to the Mustache templating language created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
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.
Install
dotnet add package Handlebars.Net
Usage
stringsource=@"<div class=""entry""> <h1>{{title}}</h1> <div class=""body""> {{body}} </div></div>";
vartemplate=Handlebars.Compile(source);
vardata=new {
title="My new post",
body="This is my first post!"
};
varresult=template(data);
/* Would render:<div class="entry"> <h1>My New Post</h1> <div class="body"> This is my first post! </div></div>*/
Registering Partials
stringsource=@"<h2>Names</h2>{{#names}} {{> user}}{{/names}}";
stringpartialSource=@"<strong>{{name}}</strong>";
Handlebars.RegisterTemplate("user", partialSource);
vartemplate=Handlebars.Compile(source);
vardata=new {
names=new [] {
new {
name="Karen"
},
new {
name="Jon"
}
}
};
varresult=template(data);
/* Would render:<h2>Names</h2> <strong>Karen</strong> <strong>Jon</strong>*/
This will expect your views to be in the /Views folder like so:
Views\layout.hbs |<--shared as in \Views
Views\partials\somepartial.hbs <--shared as in \Views\partials
Views\{Controller}\{Action}.hbs
Views\{Controller}\{Action}\partials\somepartial.hbs
Registering Block Helpers
HandlebarsBlockHelper_stringEqualityBlockHelper= (TextWriteroutput, HelperOptionsoptions, dynamiccontext, object[] arguments) => {
if (arguments.Length!=2)
{
thrownewHandlebarsException("{{StringEqualityBlockHelper}} helper must have exactly two argument");
}
stringleft=arguments[0] asstring;
stringright=arguments[1] asstring;
if (left==right)
{
options.Template(output, null);
}
else
{
options.Inverse(output, null);
}
};
Handlebars.RegisterHelper("StringEqualityBlockHelper", _stringEqualityBlockHelper);
Dictionary<string, string> animals=newDictionary<string, string>() {
{"Fluffy", "cat" },
{"Fido", "dog" },
{"Chewy", "hamster" }
};
stringtemplate="{{#each @value}}The animal, {{@key}}, {{StringEqualityBlockHelper @value 'dog'}}is a dog{{else}}is not a dog{{/StringEqualityBlockHelper}}.\r\n{{/each}}";
Func<object, string> compiledTemplate=Handlebars.Compile(template);
stringtemplateOutput=compiledTemplate(animals);
/* Would renderThe animal, Fluffy, is not a dog.The animal, Fido, is a dog.The animal, Chewy, is not a dog.*/
Performance
Compilation
Compared to rendering, compiling is a fairly intensive process. While both are still measured in millseconds, compilation accounts for the most of that time by far. So, it is generally ideal to compile once and cache the resulting function to be re-used for the life of your process.
Rendering
Nearly all time spent in rendering is in the routine that resolves values against the model. Different types of objects have different performance characteristics when used as models.
Model Types
The absolute fastest model is a dictionary (microseconds), because no reflection is necessary at render time.
The next fastest is a POCO (typically a few milliseconds for an average-sized template and model), which uses traditional reflection and is fairly fast.
Rendering starts to get slower (into the tens of milliseconds or more) on dynamic objects.
The slowest (up to hundreds of milliseconds or worse) tend to be objects with custom type implementations (such as ICustomTypeDescriptor) that are not optimized for heavy reflection.
A frequent performance issue that comes up is JSON.NET's JObject, which for reasons we haven't fully researched, has very slow reflection characteristics when used as a model in Handlebars.Net. A simple fix is to just use JSON.NET's built-in ability to deserialize a JSON string to an ExpandoObject instead of a JObject. This will yield nearly an order of magnitude improvement in render times on average.