A Better Razor Foreach Loop

razor, code, asp.net mvc comments edit

Yesterday, during my ASP.NET MVC 3 talk at Mix 11, I wrote a useful helper method demonstrating an advanced feature of Razor, Razor Templated Delegates.

There are many situations where I want to quickly iterate through a bunch of items in a view, and I prefer using the foreach statement. But sometimes, I need to also know the current index. So I wrote an extension method to IEnumerable<T> that accepts Razor syntax as an argument and calls that template for each item in the enumeration.

public static class HaackHelpers {
  public static HelperResult Each<TItem>(
      this IEnumerable<TItem> items, 
      Func<IndexedItem<TItem>, 
      HelperResult> template) {
    return new HelperResult(writer => {
      int index = 0;

      foreach (var item in items) {
        var result = template(new IndexedItem<TItem>(index++, item));
        result.WriteTo(writer);
      }
    });
  }
}

This method calls the template for each item in the enumeration, but instead of passing in the item itself, we wrap it in a new class, IndexedItem<T>.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

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

And here’s an example of its usage within a view. Notice that we pass in Razor markup as an argument to the method which gets called for each item. We have access to the direct item and the current index.


@model IEnumerable<Question>

<ol>
@Model.Each(@<li>Item @item.Index of @(Model.Count() - 1): @item.Item.Title</li>)
</ol>

If you want to try it out, I put the code in a package in my personal NuGet feed for my code samples. Just connect NuGet to http://nuget.haacked.com/nuget/ and Install-Package RazorForEach. The package installs this code as source files in App_Code.

UPDATE: I updated the code and package to be more efficient (4/16/2011).

Comments