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.

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. Thu, 21 Aug 2008 07:17:08 CDT

    NHaml: Block Methods and RESTful Helpers…

    You’ve been kicked (a good thing) - Trackback from DotNetKicks.com…

  2. Sun, 24 Aug 2008 15:28:55 CDT

    I hope the RestfulAction.Destroy doesn’t use a GET :)

  3. AndrewNo Gravatar Sun, 24 Aug 2008 17:12:16 CDT

    I haven’t put in the hidden form stuff yet but there is a TODO in the code :-)

  4. aCoderNo Gravatar Mon, 01 Sep 2008 09:05:16 CDT

    Any quick way make the Preview 5 of asp.net mvc (concerning the new IViewEngine and IView interfaces) play with the current trunk implementation?

  5. aCoderNo Gravatar Mon, 01 Sep 2008 09:09:38 CDT

    I was thinking of making my TemporaryNHamlViewEngineUntilTheTrunkGetsUpdated class inherting VirtualPathProviderViewEngine, and call the NHaml template compiler directly, but I have no idea how to set it up. A previous blog post showing that didn’t work…

  6. MathNo Gravatar Mon, 01 Sep 2008 16:13:28 CDT

    Hey,

    I can’t find your contact page, so I decided to use a comment (sorry). What ever happened to backgroundmotion.com? I just found out about it few weeks ago, and now it’s gone…

    Thanks!

  7. AndrewNo Gravatar Mon, 01 Sep 2008 21:39:46 CDT

    @aCoder,

    I’ll update to the latest version in the next day or so.

    @Math,

    It’s still available on CodePlex as far as I’m aware. Search for it there.

Comments