Razor with an Xslt touch

Wednesday, December 3, 2014 1:11 PM dotnet umbraco

One thing people loved about Xslt in Umbraco was the declarative syntax, so you could write the following code to apply "a template" to each child of the current page:

<xsl:apply-templates select="$currentPage/* [@isDoc]" />

And by defining the templates as follows, Xslt would automatically pick the correct template for your item, depending on its content type (product brochure vs security notice).

<xsl:template match="productBrochure">
  ...
</xsl:template>

<xsl:template match="securityNotice">
  ...
</xsl:template>

And that was quite elegant.

Enters Razor

Razor is not a declarative language, so in order to do something similar you would have to rely on quite verbose code:

@foreach (var item in Model.Children)
{
  switch (item.ContentTypeAlias)
  {
    case "ProductBrochure":
      @RenderProductBrochure(item)
      break;
    case "SecurityNotice":
      @RenderSecurityNotice(item)
      break;
  }
}

@RenderProductBrochure(IPublishedContent item)
{ ... }

@RenderSecurityNotice(IPublishedContent item)
{ ... }

Enter strongly-typed models

When using strongly-typed models (via the models factory), each item that is returned by the content cache implements IPublishedContent but also is of a specific CLR type. In our example, there would be two classes, ProductBrochure and SecurityNotice, corresponding to the two content types.

We can therefore simplify the helpers:

@helper Render(ProductBrochure item)
{ ... }

@helper Render(SecurityNotice item)
{ ... }

And then we might naïvely think that the following code will work:

@foreach (var item in Model.Children)
{
  @Render(item)
}

Unfortunately, it will not: when the compiler compiles that code, all it knows is that item is an IPublishedContent and so it looks for a rendering method accepting an IPublishedContent parameter—and ignores your renderers.

The last part of the puzzle

We would like to tell the runtime to pick the proper renderer at runtime, depending on the actual CLR type of item. Well, it turns out that that is precisely why dynamics were created in the first place. If we ask the compiler to consider item as a dynamic, it will defer binding until runtime. As a result, the following code will work and pick the proper renderer:

@foreach (var item in Model.Children)
{
  @Render((dynamic)item)
}

I am not quite fond of dynamics, because they tend to propagate and corrupt everything. But in that situation, their very local usage allows us to write elegant declarative-style views in Razor.

comments powered by Disqus