Templated Razor Delegates

asp.net mvc, code, razor 0 comments suggest edit

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 :stuck_out_tongue:  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, <span class="asp">@</span>&lt;li><span class="asp">@</span>item&lt;/li> is automatically converted into a Func&lt;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.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

30 responses

  1. Avatar for Skywalker
    Skywalker February 27th, 2011

    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!

  2. Avatar for Arun Mahendrakar
    Arun Mahendrakar February 27th, 2011

    This is really amazing. Thanks for sharing this Phil.
    Arun

  3. Avatar for Andrew Nurse
    Andrew Nurse February 27th, 2011

    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/...

  4. Avatar for Aaron Powell
    Aaron Powell February 27th, 2011

    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!

  5. Avatar for Mike
    Mike February 27th, 2011

    Now THAT'S a cool feature! Any more goodies planned for Razor 2? :)

  6. Avatar for Yngve Bakken Nilsen
    Yngve Bakken Nilsen February 27th, 2011

    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 :)

  7. Avatar for Marge
    Marge February 28th, 2011

    Uh. I miss my database? This site is scary. Where is my Join?

  8. Avatar for Lukas Kubis
    Lukas Kubis March 11th, 2011

    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/...

  9. Avatar for Shannon Deminick
    Shannon Deminick March 21st, 2011

    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?

  10. Avatar for Krzysiek Szczęsny
    Krzysiek Szczęsny March 22nd, 2011

    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

  11. Avatar for Bret Ferrier
    Bret Ferrier March 24th, 2011

    Is there a Nuget with this in it yet?

  12. Avatar for Bruce
    Bruce March 24th, 2011

    Very nice!
    Perfect Razor

  13. Avatar for Shannon Deminick
    Shannon Deminick March 30th, 2011

    FYI, the <text></text> solution above works!!! thanks Krzysiek !

  14. Avatar for Khoi Pham
    Khoi Pham April 9th, 2011

    This is a great feature, however for some reason the delegate doesn't seem to be called inside layouts.

  15. Avatar for Jeswin Kumar
    Jeswin Kumar April 14th, 2011

    Thanks for sharing. I have spent 2 hours on this.

  16. Avatar for Nova
    Nova April 16th, 2011

    Cool for Razor, will the feaute be built-in the future version of ASP.NET MVC?

  17. Avatar for Skywalker
    Skywalker August 27th, 2011

    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.

  18. Avatar for Murray
    Murray November 2nd, 2011

    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.

  19. Avatar for Murray
    Murray November 2nd, 2011

    should read:
    WrapInAnchorOrNot(node, @<span>@otherObject.Title</span>)

  20. Avatar for David
    David December 16th, 2011

    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.

  21. Avatar for Martin
    Martin January 19th, 2012

    FYI, it took me a while to figure out that HelperResult belongs to namespace System.Web.WebPages. HTH someone!

  22. Avatar for Ramya
    Ramya May 30th, 2012

    good one. Even more clear explanation about the delegate generic method would have been better.

  23. Avatar for Moi
    Moi December 13th, 2012

    'I mean, don’t both words signify being forceful? I digress.' Words yes .. but the tags as we all know are <b> and <strong>. On your case I am jumping :P

  24. Avatar for Moi
    Moi December 13th, 2012

    strike 2 .. the tags as we all know are <b> and <strong>

    editor: I fixed up both comments.

  25. Avatar for James
    James July 2nd, 2013

    Also @:@this.Html.Something() should also work

  26. Avatar for cori
    cori July 17th, 2013

    FWIW, 2 years later, the updated url for Andrew's post is http://vibrantcode.com/blog...

  27. Avatar for haacked
    haacked July 17th, 2013

    Thanks! I updated it.

  28. Avatar for rooc
    rooc January 7th, 2014

    FYI but without the trailing slash

  29. Avatar for cori
    cori April 11th, 2014

    D'oh! My bad!

  30. Avatar for Scott
    Scott July 13th, 2017

    This was fantastic. Thank you.