Dissecting Razor, part 9: Inline Helpers

In addition to normal and static helpers, Razor supports inline helpers, (also known as templates), which allow you to create lambda expressions that return markup.

An inline helper is created by writing @<tag>Content</tag> as an expression.  This creates a lambda expression that takes a parameter called item and returns a HelperResult object containing the markup.

Inline helpers are used to create functions that take markup as parameters.  For example, you might make an IfLoggedOn helper that displays content if there is a logged-in user, but shows a login link to anonymous users.  To pass the content to the helper, you can use an inline helper:

@helper IfLoggedOn(Func<MembershipUser, HelperResult> content) {
    var currentUser = Membership.GetUser();
    if (currentUser == null) {
        <i>
            To use this content, you must 
            <a href="@Href("~/Login")">log in</a>.
        </i>
    } else {
        @content(currentUser)
    }
}

You can call this method with an inline helper like this:

@IfLoggedOn(
    @<form action="@Href("Add-Comment")" method="post">
        @Html.TextArea("Comment", "Type a comment")
        <input type="submit" name="Submit" value="Add Comment" />
    </form>
)

The @content(currentUser) call in helper method is translated by the Razor compiler into a call to the overload of the Write method that takes a HelperResult (returned from the delegate).  This overload writes the content of the HelperResult to the page without escaping it (Just like a call to a normal helper method).  Functions that take inline helpers can also get the text rendered by the helper by calling ToString() on the HelperResult.

The call to the helper method is compiled to the following C# code:

Write(IfLoggedOn(
item => new System.Web.WebPages.HelperResult(__razor_template_writer => {

    WriteLiteralTo(@__razor_template_writer, "    ");
    WriteLiteralTo(@__razor_template_writer, "<form action=\"");

    WriteTo(@__razor_template_writer, Href("Add-Comment"));

    WriteLiteralTo(@__razor_template_writer, "\" method=\"post\">\r\n        ");

    WriteTo(@__razor_template_writer, Html.TextArea("Comment", "Type a comment"));

    WriteLiteralTo(@__razor_template_writer, "\r\n   ...  </form>\r\n");

})));

The IsLoggedOn method is passed a lambda expression that takes an item parameter and returns a new HelperResult.  The HelperResult is constructed exactly like a normal helper, except that it’s in a lambda expression instead of a normal method.

Like normal helpers, then markup inside inline helpers can contain arbitrary code blocks and code nuggets.  However, inline helpers cannot be nested (since that would create conflicting parameter names)

The item parameter is implicitly typed based on the delegate type that the helper is being passed as (just like normal lambda expressions).  This allows inline helpers to accept a parameter:

@IfLoggedOn(@<text>Welcome, @item.UserName!</text>)

(The special <text> tag is used to pass markup without creating an actual HTML tag)

Next Time: Sections