Skip to content

Repository files navigation

Compiled Handlebars for C#

Compiling Handlebars-Templates into native C# code for performant and type-safe serverside HTML rendering.

About

This compiler is the result of Jakob Demler's bachelor thesis which covers the approach, design decisions, implementation details and its evaluation and can be found here

Project Status

This project is currently in a beta phase.

Features

  • Get your type-errors at design-time
  • Resolving partial and helper calls at design-time
  • Trivial invocation: TemplateName.Render(viewModel);
  • Minimal dependencies: resulting code just depends on System, System. Net and System.Text

Performance

A performance comparison was made against the microbenchmarks from the handlebars.js repository: https://github.com/wycats/handlebars.js/tree/master/bench

Microbenchmarkhandlebars.jsCompiledHandlebarsSpeedup
arrayeach391 ops/ms3039 ops/ms7.77
complex120 ops/ms1180 ops/ms9.83
data295 ops/ms1333 ops/ms4.51
depth1228 ops/ms3693 ops/ms16.20
depth263 ops/ms1515 ops/ms24.04
partial-recursion125 ops/ms1895 ops/ms15.16
partial211 ops/ms905 ops/ms4.29
paths2060 ops/ms4646 ops/ms2.25
string5563 ops/ms13964 ops/ms2.51
variables1991 ops/ms4027 ops/ms2.02

Usage

Visual Studio Integration

Just install the vsix package from the CustomTool project and restart Visual Studio. Add a new Handlebars template to your solution (ending on .hbs) and then add "HandlebarsCompiler" to the file's CustomTool property. The compiler will be invoked every time the Handlebar template is saved and will create a {templatename}.hbs.cs file containing the generated code.

Basic Usage

Every Handlebars-Template needs a type which it renders. As an example a class named "PersonModel" will serve:

namespaceViewModels{publicclassPersonModel{publicstringFirstName{get;set;}publicstringLastName{get;set;}publicintAge{get;set;}publicList<string>EMailAddresses{get;set;}}}

Now, we must communicate the type to the HandlebarsCompiler. At the beginning of each Handlebars-Template there needs to be a socalled ModelToken:

{{modelNamespacePath.ClassName}}

For our example that would be:

{{modelViewModels.PersonModel}}

Note, that the type inside the ModelToken can also be a base class or interface of the actual ViewModel that is passed to the template.

The rest of the Handlebars-Templates follows standard Handlebars syntax and semantics. Here is the full example:

{{modelViewModels.PersonModel}}
<h1>Hello {{FirstName}}{{LastName}}</h1>
<p>You are {{Age}} years old and you have
{{#ifEMailAddresses}}
following email addresses:
<ul>
{{#eachEMailAddresses}}
<li>{{this}}</li>
{{/each}}
</ul>
{{else}}
no email addresses.
{{/if}}</p>

Partials

Partials do not need to be registered. Just be sure to compile the partial before the Handlebars-Template that uses that partial. For everything else standard handlebars.js logic applies.

Helper Functions

CompiledHandlebars allow the usage of helper functions. These must be static, return a string and be annotated by an attribute. Parameters are checked if they match at compile-time so overloading is possible. Other than that, they have no restrictions an can be placed anywhere in your codebase.

[CompiledHandlebarsHelperMethod]pulicstatic string FullName(PersonModelmodel){returnstring.Concat(model.FirstName," ",model.LastName);}

Layout Functionality

CompiledHandlebars offers differing functionality regarding layouts: Any HandlabarsTemplate can be rendered inside a HandlebarsLayout. HandlebarsLayouts differ from normal HandlebarsTemplates due to a special {{body}} token:

{{modelViewModels.IPageModel}}
<!DOCTYPE html>
<head>
<title>{{Title}}</title>
{{#ifKeywords}}<metaname="keywords"content="{{Keywords}}">{{/if}}
</head>
<body>
{{body}}
</body>
</html>

To render a Handlebars-Template in that layout, use the {{layout}} Token right after the {{model}} token:

{{modelViewModels.TitlePageModel}}{{layoutMainLayout}}
<h1>{{Headline}}</h1>
<p>{{Content}}</p>

The result of that template is equal to the following template:

{{modelViewModels.TitlePageModel}}
<!DOCTYPE html>
<head>
<title>{{Title}}</title>
{{#ifKeywords}}<metaname="keywords"content="{{Keywords}}">{{/if}}
</head>
<body>
<h1>{{Headline}}</h1>
<p>{{Content}}</p>
</body>
</html>

Compatability to Handlebars.js

Special Syntax

Because of the different approach to the Handlebars.js version (e.g. statically typed Handlebars-Templates), the compiler needs to know for which type your Handlebars-Template is meant. This information is communicated to the compiler by a special ModelToken. Its syntax is straightforward:

{{modelNamespacePath.ClassName}}

For example:

{{modelHandlebarsTest.TestViewModel}}

This token needs to be at the beginning of every Handlebars-Template.

Implemented Subset

  • Html Encoding/Escaping
  • Builtin Block Helpers
  • Partials
  • Helper Functions
  • Whitespace Control

Not Implemented:

  • Mustache Blocks
  • Block Helpers
  • Subexpressions

Building

Following prerequisites are needed in order to be able to build the solution:

  • Visual Studio 2015
  • Visual Studio 2015 Extensibility Tools

About

Compiled Handlebars for C# - Compiling Handlebars-Templates into native C# code for performant and type-safe serverside HTML rendering.

Topics

Resources

Stars

9 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages