Creating Markup Actions in Razor

Razor’s inline helpers allow you to create lambda expression that return markup (as a HelperResult).  However, there is no simple way to create a lambda expression that writes HTML directly to the page (instead of returning it).

In ASPX pages, one can simply put the beginning and end of a lambda expression in expression blocks, then put markup in the middle.

For example, this code creates a delegate that writes a <b> tag to the page:

<%
    Action pageWriter = () => {%><b>I'm from a lambda!</b><%};
    pageWriter();
    pageWriter();
    pageWriter();
%>

Calling the pageWriter delegate will write directly to the HTTP response stream.

By contrast, Razor inline expressions return their markup.  To do this in a Razor page, one would write

@{
    Func<object, HelperResult> htmlMaker
         = @<b>I'm from a lambda!</b>;
    @htmlMaker(null)    //Note @ sign
    @htmlMaker(null)    //Note @ sign
    @htmlMaker(null)    //Note @ sign
}

Calling htmlMaker without an @ sign will return HTML, but won’t write anything to the page.

When working with libraries designed for ASPX pages, it can be necessary to create ASPX-style inline helpers that write to the page instead of returning a value.  You can do that by creating an inline helper lambda and passing it to the Write method:

@{
    Action pageWriter = () => Write(new Func<object, HelperResult>(
         @<b>I'm from a lambda!</b>
    )(null));

    pageWriter();
    pageWriter();
    pageWriter();
}

Like the ASPX version, pageWriter now writes directly to the page and does not return anything.

This code can be made simpler by wrapping it in a separate method:

Action MakeAction(Func<object, HelperResult> inlineHelper) {
    return () => Write(inlineHelper(null));
}

This method takes an inline helper and returns an Action that writes the helper’s output to the page.  Since it needs to call the page’s Write method (to use the page’s output stream), this method must be defined in the WebPageBase instance, either in an @functions block or in a common base class.

Any code in the inline helper will execute each time the resulting Action is called.

It can be called like this:

@{
    Action pageWriter2 = MakeAction(@<b>I'm from a lambda!</b>);

    pageWriter();
    pageWriter();
    pageWriter();
}

This code is equivalent to the previous sample, but it’s much simpler.

One can also write write a version that takes a parameter:

Action<T> MakeAction<T>(Func<T, HelperResult> inlineHelper) {
    return param => Write(inlineHelper(param));
}
Note that the type parameter must be specified explicitly; C# does not infer type parameters from the method’s return type.

1 comments:

Thanks for this information. I found it very useful in coming up with my own solution.

Just to explain what I wanted to do, I wanted to pass around markup just as you would pass around variables of any other type. However, sometimes when you use variables within a markup block and that variable changes (for example during a for-loop), it prints the last value that the variable was assigned (as opposed to the value it held at the time).

Therefore what I did was to strictly evaluate the markup passed in.

The following is my solution:

HtmlString Markup(Func<dynamic, HelperResult> markupExpr)
{
return new HtmlString(markupExpr(null).ToHtmlString());
}

and you can call it as such:

@{
var markup = Markup(@<span>...</span>);
}

(at this point you can put it in a list etc).

and then you can call it:

@markup

Post a Comment