Better templating with Razor delegates
09 Jan

Better templating with Razor delegates

  When asked "what is your favorite templating engine?" My standard answer is KnockoutJS as it leaves the templating to the client-side JS. I feel that the more robust the server-side templating language is, the greater the temptation to put logic into it.

  Razor is one of those engines. It has all the power of C# and most of its bells and whistles encourage you to add logic inside the markup itself. One of the few exceptions however, are razor delegates, which allow you to simply and elegently apply logic that is nested in the model, or set in place by the controller, while keeping the markup that is applied in the view where it belongs.

  Let us say you want to create a widget engine. You have 2 widgets: "Text Box", which is multi-line, and "Text Field" which is not. Your model has an Stack each with a Type that defines which widget each item is. The markup will want these widgets as it needs them and as such does not care which one is next in the Collection, but the next content that is of the type it is looking for.

  To solve this problem we could split the content into multiple stacks in the model based on type, but we would have to handle the order and which is next, all in the view, which is what I'm trying to avoid.

Razor

@{ var nextBox = Model.Boxes.Pop() } 
@{ var nextField = Model.Fields.Pop() }
<div id="@nextBox.HtmlId" contentId="@nextBox.Id" pageAreaId="@nextBox.PageAreaId">
    @Html.Raw(nextBox.Content)
</div>

  To make matters worse each time I utilize one of the Fields I have Pop() it off of the stack.

Razor

@{ nextField = Model.Fields.Pop() }
<input id="@nextField.HtmlId" contentId="@nextField.Id" value=nextBox.Content />

  What if I move the collection processing into the model. So all I have to do is call "GetNext(typeId)" and it will return to me the next content in it's respective stack. Then my view becomes:

Razor

@{ var content = Model.Content.GetNext(Widgets.TextBox) }
<div id="@content.HtmlId" contentId="@content.Id" pageAreaId="@content.PageAreaId">
    @Html.Raw(content.Content)
</div>

  This is better, but I still have to call GetNext and assign it to a variable everytime. To fix this we will use a razor delegate. A simple Razor delegate looks like this:

Razor

<div class="section clearfix block-section">
    @Model.Widgets.GetNext(3).Render(
    @<div id="@item.HtmlId" contentId="@item.Id" pageAreaId="@item.PageAreaId" >
        @Html.Raw(item.Content)
    </div>)
</div>

C#

public static HelperResult Render(this IGenericContent content,
Func<IGenericContent, HelperResult> formatter)
{
    return new HelperResult(
        (writer) => formatter(content).WriteTo(writer)
    );
}

  It basically allows you to pass Razor markup into your model and apply it there, while still defining it where it belongs. In this case the markup translates to a Func<IGenericContent, HelperResult>.

  The part about Razor delegates that isn't immediately evident is that the when defining the delegate, the Model you pass in (in this case IGenericContent) is always defined as "item." Which is a strangely specific, even for C#. I would expect it to allow some kind of (content) => @<div... syntax to allow you to name it yourself, but that is not the case. It is a small price to pay for such a neat feature.