Templated Razor Delegates

David Fowler turned me on to a really cool feature of Razor I hadn’t realized made it into 1.0, Templated Razor Delegates. What’s that? I’ll let the code do the speaking.

@{
  Func<dynamic, object> b = @<strong>@item</strong>;
}
<span>This sentence is @b("In Bold").</span>

That could come in handy if you have friends who’ll jump on your case for using the bold tag instead of the strong tag because it’s “not semantic”. Yeah, I’m looking at you Damian Smile with tongue out. I mean, don’t both words signify being forceful? I digress.

Note that the delegate that’s generated is a Func<T, HelperResult>. Also, the @item parameter is a special magic parameter. These delegates are only allowed one such parameter, but the template can call into that parameter as many times as it needs.

The example I showed is pretty trivial. I know what you’re thinking. Why not use a helper? Show me an example where this is really useful. Ok, you got it!

Suppose I wrote this really cool HTML helper method for generating any kind of list.

public static class RazorExtensions {
    public static HelperResult List<T>(this IEnumerable<T> items, 
      Func<T, HelperResult> template) {
        return new HelperResult(writer => {
            foreach (var item in items) {
                template(item).WriteTo(writer);
            }
        });
    }
}

This List method accepts a templated Razor delegate, so we can call it like so.

@{
  var items = new[] { "one", "two", "three" };
}

<ul>
@items.List(@<li>@item</li>)
</ul>

As I mentioned earlier, notice that the argument to this method, @<li>@item</li> is automatically converted into a Func<dynamic, HelperResult> which is what our method requires.

Now this List method is very reusable. Let’s use it to generate a table of comic books.

@{
    var comics = new[] { 
        new ComicBook {Title = "Groo", Publisher = "Dark Horse Comics"},
        new ComicBook {Title = "Spiderman", Publisher = "Marvel"}
    };
}

<table>
@comics.List(
  @<tr>
    <td>@item.Title</td>
    <td>@item.Publisher</td>
  </tr>)
</table>

This feature was originally implemented to support the WebGrid helper method, but I’m sure you’ll think of other creative ways to take advantage of it.

If you’re interested in how this feature works under the covers, check out this blog post by Andrew Nurse.

What others have said

Requesting Gravatar... Skywalker Feb 27, 2011 6:59 AM
# re: Templated Razor Delegates
Cool!! That's actually really poweful and helps creating reusable b("Templated") helpers! What's even more fun is that with this we can write HTML helpers that accept multiple delegates, where each delegate represents user-controlled rendering of certain parts of some widget being rendered. I have been waiting for this since MVC 1.0. Now it's easy. Way to go, Razor!
Requesting Gravatar... Arun Mahendrakar Feb 27, 2011 11:20 AM
# re: Templated Razor Delegates
This is really amazing. Thanks for sharing this Phil.

Arun
Requesting Gravatar... Andrew Nurse Feb 27, 2011 12:34 PM
# re: Templated Razor Delegates
Glad to see this feature get some exposure :). Though there's a typo in your first sample, you need an "@" before the call to "b".

Also, if anyone is interested in how this feature works under the covers, I have a post about it on my blog: blog.andrewnurse.net/...
Requesting Gravatar... Aaron Powell Feb 27, 2011 1:40 PM
# re: Templated Razor Delegates
Phil - you'll be glad to know that during webcamps in Sydney Damian actually typed a 'b' tag while on stage. To his credit he did delete quickly, but not before we got to tweeting about it!
Requesting Gravatar... Mike Feb 27, 2011 2:29 PM
# re: Templated Razor Delegates
Now THAT'S a cool feature! Any more goodies planned for Razor 2? :)
Requesting Gravatar... Yngve Bakken Nilsen Feb 27, 2011 3:53 PM
# HTML5
This could potentially be a great feature in terms of making HTML5 views with a nice fallback to older versions:

@{
bool html5 = false;
Func<dynamic, object> html5article = @<article>@item</article>;
Func<dynamic, object> html4article = @<div class='article'>@item</div class='article'>;
Func<dynamic, object> article = html5 ? html5article : html4article;
}
@article("Here's some article for you!");


Now just fix the bool html5 = false; to actually check for html5 support, and we're done :)
Requesting Gravatar... Marge Feb 28, 2011 3:46 AM
# re: Templated Razor Delegates
Uh. I miss my database? This site is scary. Where is my Join?
Requesting Gravatar... Lukas Kubis Mar 11, 2011 2:32 PM
# re: Templated Razor Delegates
Great post, but if you combine this feature with declarative html helpers , it will be powerfull ... look at my post about declarative html helpers blog.lukaskubis.net/...
Requesting Gravatar... Shannon Deminick Mar 21, 2011 9:03 PM
# re: Templated Razor Delegates
It seems as though the razor parser only converts anything to a templated razor delegate by finding "@<" together so you can't actually use a Helper method itself as the template (such as @Html.TextBoxFor...).

So if you want to include Helper methods in the razor template that you are passing to another Helper method (even if all you want to render is the output of your Helper) you have to wrap it in some html such as:

@<span>Html.TextBoxFor(x => item.Name)</span>

Is there any nice way around this?
Requesting Gravatar... Krzysiek Szczęsny Mar 22, 2011 6:04 PM
# re: Templated Razor Delegates
I think you can work around the @< issue with <text> tag - razor has this for "text only" nodes, so if my guess is correct, @<text>Html.TextBoxFor(...)</text> should render the text box without a wrapper
Requesting Gravatar... Bret @runxc1 Mar 24, 2011 6:49 AM
# re: Templated Razor Delegates
Is there a Nuget with this in it yet?
Requesting Gravatar... Bruce Mar 24, 2011 1:46 PM
# re: Templated Razor Delegates
Very nice!
Perfect Razor
Requesting Gravatar... Shannon Deminick Mar 30, 2011 7:10 AM
# re: Templated Razor Delegates
FYI, the <text></text> solution above works!!! thanks Krzysiek !
Requesting Gravatar... Khoi Pham Apr 10, 2011 2:42 AM
# re: Templated Razor Delegates
This is a great feature, however for some reason the delegate doesn't seem to be called inside layouts.

Requesting Gravatar... Jeswin Kumar Apr 14, 2011 8:54 PM
# re: Templated Razor Delegates
Thanks for sharing. I have spent 2 hours on this.
Requesting Gravatar... Nova Apr 16, 2011 3:48 PM
# re: Templated Razor Delegates
Cool for Razor, will the feaute be built-in the future version of ASP.NET MVC?
Requesting Gravatar... Skywalker Aug 27, 2011 2:17 PM
# re: Templated Razor Delegates
Taking templated Razor delegates a step further, I thought it would be really powerful if we could easily create UI components that provide several settings to customize its appearance and enable the user of the component to pass in multiple razor delegates that act as content templates.

To avoid having to pass all that into a list of arguments, one could obviously create a view model class and pass an instance into the templated razor function.

That would work. But it would be even nicer if we could use a fluent syntax describing our view model.

And taking that even a step further, you could go as far as resuing the view model in other projects (or even within the same project) using different partial views!

I started a small expeiment on this at codeplex: http://mvcviewcomponents.codeplex.com/

The idea is that we speak in terms of ViewComponents, rather than HTML helpers. The core library enables developers to build a rich set of UI components, using things like inheritance & redistribution of the components (much like the Telerik MVC suite, only now we open up a framework, paving the way for a concept like ViewComponents).

I'm curious on other people's thoughts on this.
Requesting Gravatar... Murray Nov 02, 2011 11:05 AM
# re: Templated Razor Delegates
I'm trying to do something similar with a single dynamic object input rather than IEnumerable<T> any idea how I'd do that?

I want to end up with something like
WrapInAnchorOrNot(node, @&lt;span&gt;various junk&lt;/span&gt;)

Where node is a dynamic object which I can dig into to determine if an anchor is required or not.

Cheers.
Murray.
Requesting Gravatar... Murray Nov 02, 2011 11:07 AM
# re: Templated Razor Delegates
should read:
WrapInAnchorOrNot(node, @<span>@otherObject.Title</span>)
Requesting Gravatar... David Dec 16, 2011 8:32 PM
# re: Templated Razor Delegates
Nice feature. Is this also supported in vb.net? Because I'm not able to get it to work.

<Extension()>
Public Function List(Of T)(ByVal items As IEnumerable(Of T), ByVal template As Func(Of T, HelperResult)) As HelperResult

Return New HelperResult(Sub(writer)
For Each item In items
template(item).WriteTo(writer)
Next
End Sub
)
End Function

@Code
Dim items = {"one", "two"}
End Code

@items.List(Function(item) @<li>@item</li> End Function)

Always results in Object reference not set to an instance of an object.
Requesting Gravatar... Martin Jan 19, 2012 11:02 PM
# re: Templated Razor Delegates
FYI, it took me a while to figure out that HelperResult belongs to namespace System.Web.WebPages. HTH someone!

What do you have to say?

(will show your gravatar)
Please add 6 and 8 and type the answer here: