Introducing NHaml - An ASP.NET MVC View Engine

Posted by Andrew on December 19, 2007

NHaml (pronounced enamel) is a pure .NET implementation of the popular view engine. From the Haml website:

“Haml is a markup language that‘s used to cleanly and simply describe the XHTML of any web document, without the use of inline code. Haml functions as a replacement for inline page templating systems such as PHP, ERB, and ASP. However, Haml avoids the need for explicitly coding XHTML into the template, because it is actually an abstract description of the XHTML, with some code to generate dynamic content.”

In other words, NHaml is an external for XHTML. It’s primary qualities are it’s simplicity, terseness, performance and that it outputs nicely formatted XHTML. Additionally, the NHaml view engine provides support for Rails style layouts and partials – more on that below.

An example is the best way to grok how NHaml works. Below we have a typical view targeting the default Web Forms view engine. Observe the proliferation of angle-brackets, closing tags and general cruft.

~/Views/Products/List.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" 
    CodeBehind="List.aspx" Inherits="MvcApplication5.Views.Products.List" Title="Products" %>
<asp:Content ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
  <h2><%= ViewData.CategoryName %></h2>
  <ul>
    <% foreach (var product in ViewData.Products) { %>
      <li>
        <%= product.ProductName %> 
        <div class="editlink">
          (<%= Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })%>)
        </div>
      </li>
    <% } %>
  </ul>
  <%= Html.ActionLink("Add New Product", new { Action="New" }) %>
</asp:Content>

Now, let us bask in the glory of the NHaml version:

~/Views/Products/List.haml

%h2= ViewData.CategoryName
%ul
  - foreach (var product in ViewData.Products)
    %li
      = product.ProductName 
      .editlink
        = Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })
= Html.ActionLink("Add New Product", new { Action="New" })

There are a couple of things going on here. First, NHaml uses indentation (2 spaces) as an alternative to closing tags. Second, the first non-whitespace character in a line can describe how the line should be processed. For example, the first line in the example above starts with a ’%’, which directs NHaml to process that line as an XHTML tag. These directives are called Markup Rules and are discussed in detail in the Reference section below.

Download & License

NHaml is open source and released under the MIT license so go ahead and download the source, kick the tires and let me know what you think. Oh, and if/when you find an issue let me know or submit a patch.

Using NHaml

Configuring the ASP.NET MVC framework to use NHaml is as simple as referencing Mindscape.NHaml.ViewEngine.dll and putting the following line at the end of the Application_Start method in the Global.asax.cs file.

ControllerBuilder.Current.SetDefaultControllerFactory(typeof(NHamlControllerFactory));

NHaml view templates should have a .haml extension and are placed under the Views project folder in the normal manner.

Partials & Layouts

Currently, unlike Rails, the ASP.NET MVC framework delegates layout and partial handling to the view engine. Therefore, NHaml provides it’s own layout & partial system.

Partials

Partials are small reusable sub-views available within a single controller context. NHaml implements a Rails-like partial system. Any view template beginning with an ‘_’ is considered a partial. To use a partial simply use the NHaml ‘_’ markup rule like so:

~/Views/Products/List.haml

- foreach (var product in ViewData.Products)
  %li
    _ Product

In this example NHaml will replace the line ”_ Product” with the contents of the file _Product.haml in the current controller”s view folder (~/Views/Products)

~/Views/Products/_Product.haml

= product.ProductName 
%span.editlink
  = Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })

At compile-time, both layouts and partials are merged into a single view and so any ViewData context is available.

Layouts

Layouts are the NHaml equivalent of master pages. NHaml provides a Rails-like layout system. Layouts are applied automatically based on the following conventions:

  1. If a layout is specified through the masterName argument of RenderView then that layout is applied. Or,
  2. If a layout with the same name as the controller exists in Views/Shared then it is applied. Or,
  3. If a layout called Application.haml exists in the Views/Shared folder then it is applied.

Here is an example layout targeting the Products controller (Views/Shared/Products.haml)

~/Views/Products/Products.haml

!!!
%html{xmlns="http://www.w3.org/1999/xhtml"}
  %head
    %title My Sample MVC Application
    %link{href="../../Content/Sites", rel="stylesheet", type="text/css"}
  %body
    #inner
      #header
        %h1 My Store Manager - Products Section
      #menu
        %ul
          %li
            %a{href="/"} Home
          %li
            = Html.ActionLink("About Us", "About", "Home")
          %li
            = Html.ActionLink("Products", new { Controller = "Products", Action = "Category", ID = 1 })
      #maincontent
        _
      #footer

A layout file uses the ’_’ partial operator with no arguments to signify where view content should be inserted into the layout.

Compilation Model

NHaml views are compiled. The first time an NHaml view is requested it is parsed and compiled into a .NET Type capable of rendering the view. This Type is then cached. The view engine can run in two modes: development or production. In development mode, the view engine will automatically recompile a cached view if any of it”s constituent haml files are modified. Production mode may be enabled through the web.config file like so:

<configuration>
    <configSections>
    <section name="nhamlViewEngine" 
             type="Mindscape.NHaml.ViewEngine.Configuration.NHamlViewEngineSection, Mindscape.NHaml.ViewEngine" />
  </configSections>
  <nhamlViewEngine production="true" />
</configuration>

ViewData & Helpers

NHaml views inherit from an NHamlView base class that makes available the standard ASP.NET MVC helpers: AjaxHelper, HtmlHelper and UrlHelper along with a
ViewData property. These can be accessed and used in the same way as per the Web Forms view engine. For example,

%li
  = Html.ActionLink("About Us", "About", "Home")

results in:

<li>
  <a href="/Home/About">About Us</a>
</li>

NHaml Language Reference

Disclaimer: Most of this reference was lifted from the main Haml site.

Tags %

The ’% character is placed at the beginning of a line. It‘s followed immediately by the name of an element, then optionally by modifiers (see below), a space, and text to be rendered inside the element. It creates an element in the form of <element></element>. For example:

%one
  %two
    %three Hey there

is compiled to:

<one>
  <two>
    <three>Hey there</three>
  </two>
</one>

Any string is a valid element name; NHaml will automatically generate opening and closing tags for any element.

Attributes {}

Braces represent a C# 3 anonymous type object initializer statement that is used for specifying the attributes of an element. It is evaluated as an anonymous type, so
logic will work in it and local variables may be used. The braces are placed after the tag is defined. For example:

%head
  %title My Sample MVC Application
  %link{ href="../../Content/Sites", rel="stylesheet", type="text/css" }

is compiled to:

<head>
  <title>My Sample MVC Application</title>
  <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

Self-closing Tags /

The ’/’ character, when placed at the end of a tag definition, causes the tag to be self-closed. For example:

%br/
%meta{ http_equiv" = "Content-Type", content = "text/html"}/

is compiled to:

<br />
<meta http-equiv="Content-Type" content="text/html" />

Some tags are automatically closed, as long as they have no content. meta, img, link, script, br, input and hr tags are closed by default.

%br
%meta{ http_equiv = "Content-Type", content = "text/html" }

is also compiled to:

<br />
<meta http-equiv="Content-Type" content="text/html" />

Class and Id . and #

The ’.’ and ’#’ are borrowed from CSS. They are used as shortcuts to specify the class and id attributes of an element, respectively. Multiple class names can be specified in a similar way to CSS, by chaining the class names together with periods. They are placed immediately after the tag and before an attributes hash. For example:

%div#things
  %span#rice Chicken Fried
  %p.beans{ food = "true" } The magical fruit
  %h1.class.otherclass#id La La La

is compiled to:

<div id="things">
  <span id="rice">Chicken Fried</span>
  <p class="beans" food="true">The magical fruit</p>
  <h1 class="class otherclass" id="id">La La La</h1>
</div>

And,

#content
  .articles
    .article.title
      Doogie Howser Comes Out
    .article.date
      2006-11-05
    .article.entry
      Neil Patrick Harris would like to dispel any rumors that he is straight

is compiled to:

<div id="content">
  <div class="articles">
    <div class="article title">Doogie Howser Comes Out</div>
    <div class="article date">2006-11-05</div>
    <div class="article entry">
      Neil Patrick Harris would like to dispel any rumors that he is straight
    </div>
  </div>
</div>

Implicit Div Elements

Because the div element is used so often, it is the default element. If you only define a class and/or id using the . or # syntax, a div element is automatically
used. For example:

#collection
  .item
    .description What a cool item!

is the same as:

%div{ id = "collection" }
  %div{ class = "item" }
    %div{ class = "description" } What a cool item!

and is compiled to:

<div id="collection">
  <div class="item">
    <div class="description">What a cool item!</div>
  </div>
</div>

Evaluate C# Output =

’=’ is placed at the end of a tag definition, after class, id, and attribute declarations. It‘s just a shortcut for inserting C# code into an element. It works the same as = without a tag: it inserts the result of the C# code into the template.

%p= string.Join(" ", new string[]{"He", "braid", "runner!"})

Results in:

<p>He braid runner!</p>

No Special Character

If no special character appears at the beginning of a line, the line is rendered as plain text. For example:

%gee
  %whiz
    Wow this is cool!

is compiled to:

<gee>
  <whiz>
    Wow this is cool!
  </whiz>
</gee>

DOCTYPES !!!

When describing XHTML documents with NHaml, you can have a document type or XML prolog generated automatically by including the characters !!!. For example:

!!! XML
!!!
%html
  %head
    %title Myspace
  %body
    %h1 I am the international space station
    %p Sign my guestbook

is compiled to:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <title>Myspace</title>
  </head>
  <body>
    <h1>I am the international space station</h1>
    <p>Sign my guestbook</p>
  </body>
</html>

You can also specify the version and type of XHTML after the !!!. XHTML 1.0 Strict, Transitional, and Frameset and XHTML 1.1 are supported. The default version is 1.0 and the default type is Transitional. For example:

!!! 1.1

is compiled to:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

and

!!! Strict

is compiled to:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

If you‘re not using the UTF-8 characterset for your document, you can specify which encoding should appear in the XML prolog in a similar way. For example:

!!! XML iso-8859-1

is compiled to:

<?xml version="1.0" encoding="iso-8859-1" ?>

XHTML Comments /

The ’/’ character, when placed at the beginning of a line, wraps all text after it in an HTML comment. For example:

%billabong
  / This is the billabong element
  I like billabongs!

is compiled to:

<billabong>
  <!-- This is the billabong element -->
  I like billabongs!
</billabong>

The forward slash can also wrap indented sections of code. For example:

/
  %p This doesn"t render...
  %div
    %h1 Because it"s commented out!

is compiled to:

<!--
  <p>
This doesn"t render...</p>
  <div>
    <h1>Because it"s commented out!</h1>
  </div>
-->

You can also use Internet Explorer conditional comments (about) by enclosing the condition in square brackets after the /. For example:

/[if IE]
  %a{ :href = "http://www.mozilla.com/en-US/firefox/" }
    %h1 Get Firefox

is compiled to:

<!--[if IE]>

  <a href="http://www.mozilla.com/en-US/firefox/">
    <h1>Get Firefox</h1>
  </a>
<![endif]-->

Escape Sequence \

The backslash character escapes the first character of a line, allowing use of otherwise interpreted characters as plain text. For example:

%title
  = @title
  \- MySite

is compiled to:

<title>
  MyPage
  - MySite
</title>

Line Continuation |

The pipe character designates a multiline string. It‘s placed at the end of a line and means that all following lines that end with | will be evaluated as though they were on the same line. For example:

%whoo
  %hoo I think this might get |
    pretty long so I should |
    probably make it |
    multiline so it doesn"t |
    look awful. |
  %p This is short.

is compiled to:

<whoo>
  <hoo>
    I think this might get pretty long so I should probably make it multiline so it doesn"t look awful.
  </hoo>
  <p>This is short</p>
</whoo>

Partials & Layouts _

Use an ’_’ to render a partial or the content of a layout. For a partial, specify the name of the partial after the underscore like so:

%p
  _ Customer

will render the _Customer.haml partial file.

An ’_’ on it”s own is used to specify where the content within a layout will be inserted:

%p
  _

Evaluate Output =

The ’=’ character is followed by C# code, which is evaluated and the output inserted into the document as plain text. For example:

%p
  = "hi" + " there" + " reader!" 
  = "yo"

is compiled to:

<p>
  hi there reader!
  yo
</p>

Evaluate Silent -

The ’-’ character makes the text following it into “silent” code: C# code that is evaluated, but not output.

It is not recommended that you use this widely; almost all processing code and logic should be restricted to the Controller, Helpers, or partials.

For example:

- string foo = "hello" 
- foo += " there" 
- foo += " you!" 
%p= foo

is compiled to:

<p>
  hello there you!
</p>

Code Blocks

C# code blocks, like XHTML tags, don‘t need to be explicitly closed in NHaml. Rather, they‘re automatically closed, based on indentation. A block begins whenever the indentation is increased after a silent script command. It ends when the indentation decreases.

- for (int i=42; i<47; i++)
  %p= i
%p See, I can count!

is compiled to:

<p>
  42
</p>
<p>
  43
</p>
<p>
  44
</p>
<p>
  45
</p>
<p>
  46
</p>
<p>See, I can count!</p>

Silent Comments -//

The hyphen followed immediately by a C# comment has the effect of a silent comment. Any text following this isn‘t rendered in the resulting document at all.

For example:

%p foo 
  -// This is a comment %p bar

is compiled to:

<p>
  foo
</p>