Sunday, 1 January 2012

Razor extension method Each

It is possible to augment razor views with not only helpers, but also razor extension methods.

In this example, an extension method which will take a template and from this template generate a rendered result of a list of the IEnumerable of items passed in for the extension method will be presented. It is named Each and was created by Phil Haack.

I will present the source code quickly to just repeat Phil Haack's code. Let's first add a new class called IndexedItemModel:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestActionMethodSelectorAttribute.Extensions
{
public class IndexedItem<TModel>
{
public IndexedItem(int index, TModel item)
{
this.Index = index;
this.Item = item;
}

public int Index { get; private set; }
public TModel Item { get; private set; }

}
}


Let's add the extension method next:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.WebPages;

namespace TestActionMethodSelectorAttribute.Extensions
{

public static class RazorCollectionExtensions
{

public static HelperResult Each(this IEnumerable<TItem> items,
Func<IndexedItem<TItem>, HelperResult> template)
{
return new HelperResult(
writer =>
{
for (int i = 0; i < items.Count(); i++)
{
template(new IndexedItem<TItem>(i, items.ElementAt(i))).WriteTo(writer);
}
}
);
}

}

}


An example of its usage is shown next in a view:



@{
ViewBag.Title = "Home Page";

var cars = new []
{
new { name = "Audi", color = "Blue", make = "A4" },
new { name = "BMW", color = "Black", make = "M5" },
new { name = "Volvo", color = "Red", make = "240" },
new { name = "Renault", color = "Black", make = "19" }
};

}

@cars.Each(@<li>Car is an @item.Item.name @item.Item.make of color @item.Item.color</li>)



Take note of the "magic" @item property in use here. What we see here in effect is a convenient way to iterate over a list of objects, in this case an array of anonymous objects which we call "cars" and we just add in some car objects to this array using a collection initializer. Then we output this list with our new Each method which is a razor extension method and specify our template. You need to prefix the first letter in the parenthesis with the @ sign, then use @item to refer to the object. In our Each method we use IndexedModel type for each item in the list. This got both an index and an Item of type TModel (TItem) passing in our item type, in this case the anonymous type that constitutes the "cars" object.

Thanks to Phil Haack for this nice method! I like this Each method a lot. No need to write that long @foreach razor helper any more. This also should fit nicely into linq expressions like:
@Model.MyList.Where(myItem => myItem.Age > 19).Each(@
<li>@item.Name </li>)
to output a list of the name propery for all people above 19 years old in our Model.MyList property, just to take an example.

This nice method was presented by Phil Haack here:



Note for those viewing this video, not everything went smooth for Phil Haack in this demonstration, however there were lots of "hidden gems" he presented there at the MIX11 conference in Las Vegas.

Disclaimer: I added this blog post to extract the source code from his demo into an easily available sample.

It should be relatively easy now to start creating Razor extension methods as an alternative to HTML helpers and Razor html helpers in MVC by taking a look at this example. Note the use of HelperResult and WriteTo (corresponding Response.Write in ASP.NET).

1 comment: