Inside Razor - Part 3 - Templates

One of the features of Razor which hasn't been discussed a lot is the Inline Template feature.  Razor includes the ability to provide an inline Razor template as an argument to a method.  At the moment, this is only used by the Grid helper in ASP.Net Web Pages (and Scott Guthrie showed it back in his original blog post).  However, we don't have much documentation on how to create your own templated helpers yet, so I figured I'd talk a bit about it.

First, let's take a look at what code is generated when we use an inline template.  I've written a sample templated helper called "Repeat" which just repeats the content of the template a specified number of times (we'll take a look at the implementation later).  The page that uses this helper looks something like this:


<!DOCTYPE html>
<html>
     <head>
         <title>Repeat Helper Demo</title>
     </head>
     <body>
         <p>Repeat Helper</p>
         <ul>
             @Repeat(10, @<li>List Item</li>);
         </ul>
     </body>
</html>

And when we run it, we get the following output:

Screen shot of rendered HTML.

Let's take a look at the implementation of "Repeat".  I wrote it inline in the page in this case, but you could just as easily write it in a static class in App_Code and reference it that way.  In Razor, the "@functions" block lets you write code that will be injected as-is into the body of the generated class.

@using System.Text;
@functions {
      public static IHtmlString Repeat(int times, Func<int, object> template) {
           StringBuilder builder = new StringBuilder();
          for(int i = 0; i < times; i++) {
              builder.Append(template(i));
          }
          return new HtmlString(builder.ToString());
      }
}

So, the template is being passed in as a Func<int, object> and when we invoke it, we get back the result of running the template.  But, if you look at line 6, you'll notice we're passing in an argument to the template function.  Let's take a look at the C# that is generated when we write a call to Repeat.  Here's the generated code to match the Razor code in the first code listing:

this.Write(Repeat(10,item => new Microsoft.WebPages.Helpers.HelperResult(__writer => {
     @__writer.Write(" ");
     @__writer.Write("<li>List Item</li>");
})));

It's a little complex looking, but essentially, what's happening is that we're writing out a lambda which accepts a single parameter called "item" (the type of which is determined by the method you're passing it to).  When that lambda runs, we construct and return a HelperResult.  HelperResult is a class defined in the ASP.Net Web Pages framework, and it's essentially a wrapper around yet another delegate which writes text to a TextWriter.  Think of it as a mini Razor template, when you invoke the delegate, it writes the content of the template.  The advantage of wrapping the delegate up in the HelperResult class is that we can treat it just like a string in most places since it overrides ToString to return the result of executing the template.

The "item" parameter is used in helpers like the Grid helper to provide the current data item to the template so that it can be used.  The Repeat helper passes in the iteration number as this parameter, which we can access from within the template by using "@item"?.  For example:

@Repeat(10, @<li>List Item #@item</li>);

Which will render:

ss2

In summary, if you want to use Razor templates in your helper methods, just add a parameter with the type Func<?, object> where ? can be any type you want.  As an interesting little exercise, try converting the Repeat helper to take an IEnumerable<T> and pass each item of that enumerable to the template, rendering the result.

I've uploaded the Razor file containing my sample helper here: RepeatHelper.cshtml.txt (.56 KB) (Note: To avoid issues with file types on my hoster, it has a .txt extension, just remove that and you're good to go!)

Please feel free to ask questions in the comments, or by emailing me at andrew AT andrewnurse DOT net.  I'm also on twitter at @anurse and I keep an eye on the "razor" tag on StackOverflow so there's no shortage of ways to ask!