NHaml: Block Methods and RESTful Helpers

Posted by Andrew on August 21, 2008

New in the are a couple of cool features worth mentioning:

Block Methods

Block Methods are helper methods that accept a block of NHaml markup as a final argument.

An example (albeit contrived) to demonstrate:

- Html.Tag("div") () =>
  Some regular NHaml markup here...

which results in:

<div>
  Some regular NHaml markup here...
</div>

And the Tag method looks like this:

public void Tag(string name, Action yield)
{
  _output.WriteLine("<" + name + ">");
  yield();
  _output.WriteLine("</" + name + ">");
}

As we can see, NHaml wraps up the nested block markup into a lambda (or anonymous method if targeting C# 2.0) and passes a delegate to the block as the last argument of the helper method. Invoking the delegate causes the markup within the block to be rendered. Notice too, we can write directly to the output stream: _output is an instance of NHaml’s IOutputWriter which is passed to our helper class when it’s instantiated. It has the following methods:

void WriteLine(string value);
void Write(string value);
void Indent();
void Outdent();

Output written using these methods will be correctly indented and we can also further control the indent level if we need to write nested output.

Worth mentioning is that we can also pass arguments to a block:

- Html.Tag("div", 21, 21) (i, j) =>
  = "N was: " + (i + j)

which yields:

<div>
  N was: 42
</div>

And our helper method:

public void Tag<T1, T2>(string name, T1 t1, T2 t2, Action<T1, T2> yield)
{
  _output.WriteLine("<" + name + ">");
  yield(t1, t2);
  _output.WriteLine("</" + name + ">");
}

RESTful Helpers

Using a Block Method we can create a nice Rails-style helper that encapsulates building a form for a single model:

- var product = new Product { ProductName="Soap on a Rope" }
- Html.Form(product) (f) =>
  = f.Label(p => p.ProductName)
  = f.TextField(p => p.ProductName)
  %br
  = f.Label(p => p.UnitPrice)
  = f.TextField(p => p.UnitPrice)
  %p
    = Html.SubmitButton()

Results in:

<form action="/products.mvc/create" method="post">
  <label for="Product.ProductName">Product Name</label>
  <input type="text" name="Product.ProductName" id="Product.ProductName" value="" />
  <br />
  <label for="Product.UnitPrice">Unit Price</label>
  <input type="text" name="Product.UnitPrice" id="Product.UnitPrice" value="" />
  <p>
    <input type="submit" />
  </p>
</form>

A couple of things to note here:

  1. Our Form method takes a model object and uses the conventions of REST to generate the target action. To make this work our model needs to implement a small interface: IModel:
    public interface IModel
    {
      int Id { get; }
      bool IsNew { get; }
    }
  2. Happily, we can bind our model attributes in a strongly typed fashion using Expressions.

Here’s what our Form method looks like:

public virtual void Form<TModel>(TModel model, Action<FormBuilder<TModel>> yield)
  where TModel : IModel
{
  // this should be pluggable
  var formBuilder = new FormBuilder<TModel>(model, this, _outputWriter);
 
  formBuilder.OpenTag(FormMethod.Post, new RouteValueDictionary());
  yield(formBuilder);
  formBuilder.CloseTag();
}

RESTful Link Helpers

Finally, we can apply RESTful conventions to link helpers too. Here are a few examples of some NHaml ActionLink helpers that work using REST conventions.

= Html.ActionLink<CategoriesController>() // /categories
= Html.ActionLink<CategoriesController>(RestfulAction.New, "Add Category") // /categories/new
= Html.ActionLink(category)  // /categories/show/42
= Html.ActionLink(category, RestfulAction.Edit, "Edit Category") // /categories/edit/42
= Html.ActionLink(category, RestfulAction.Destroy, "Delete Category") // /categories/destroy/42

Thoughts/comments appreciated.

NHaml News

Posted by Andrew on July 29, 2008

A while back we moved the NHaml core templating engine out of the MVCContrib project. This was done so that the core stuff could be maintained separately and to declutter the contrib project.

NHaml now lives on Google Code .

As part of the move, I’ve added a small, stand-alone ASP.NET MVC View Engine that doesn’t require MVCContrib - useful for people not wanting a dependency on contrib.

Other changes since the move are:

- Added NHaml configuration support into the core - was previously in the view engine.
- Improved view activation performance using LCG.
- Rendered view output is now written to a provided TextWriter improving perf for view engines.
- Incorporated a simple view engine framework into core.

Enjoy,

Andrew.

NHaml Visual Studio Plugin

Posted by Andrew on May 06, 2008

Dave Newman has created a cool NHaml . Thanks Dave and keep up the good work!

Standalone NHaml

Posted by Andrew on April 19, 2008

Quite a few people have asked me how to use NHaml outside of ASP.NET MVC. Here’s how to do it:

1) Create your project and add a reference to MvcContrib.NHamlViewEngine
2) Use code like the following to compile and render a template.

var templateCompiler = new TemplateCompiler();
var viewType = templateCompiler.Compile("MyTemplate.haml");               
var view = (ICompiledView)Activator.CreateInstance(viewType);
 
string output = view.Render();

In this case the haml template is located in the same folder as the application.

As always, a good way to figure this stuff out is to take a look at the unit tests. In this case I copied the code verbatim from the NHaml TestFixtureBase class.

NHaml Moved to MVC Contrib Project

Posted by Andrew on January 07, 2008

Thanks to everyone for your overwhelming interest in NHaml so far! I’m happy to announce that the project has now been incorporated into the awesome project and I will continue to help maintain it from there. Please any feature requests/bugs or, better yet, there :-)