Wednesday, 14 August 2013

Antaris RazorEngine, Site Layouts

I have been using Antaris RazorEngine (v3) both at work and in the Asura framework used by Xizi. It is a light implementation of Razor that isn't strongly tied to MVC.

That said, documentation has been pretty thin on the ground. Especially around what I've been trying to do today which is to use the Layout functionality from within a file that is effectively in a .cshtml format.

So, the problem I am trying to address is doing ASP.Net MasterPage-like things but with isolated Razor templates using RazorEngine.

Certainly there are bits of documentation about how to use Layouts when creating and treating templates from code, but I couldn't find any complete examples on how to do it with template files.


The Layout file - mylayout.cshtml


<!DOCTYPE html>
<html>
@RenderSection("Head")
@RenderSection("Body")
</html>
This is a very basic layout comprised of the beginning and end of the markup and two section placeholders for "Head" and "Body".

That's all quite simple, but the problem is that if you are using RazorEngine in the raw way that I am, then when a template tries to resolve that layout it will fail as it is not in the template cache and I do not specify a resolver delegate to be able to do it.

Instead, the layout file must be compiled ahead of time. This can be performed by the following which, given an existing pathname for a .cshtml, will compile it into the template cache:


string viewPath = @"C:\code\razor\mylayout.cshtml";
string layoutName = @"mylayout";
if (File.Exists(viewPath))
{
ITemplate template = Razor.Resolve(viewPath);
if (template == null)
{
string templateContents = File.ReadAllText(viewPath);
Razor.Compile(
templateContents,
typeof(IDictionary<string, object>),
layoutName);
}
else
{
// already in cache
}
}
Assuming viewPath is the physical path of the mylayout.cshtml file and layoutName is the simple name that this layout will be known as in the template cache. Also, there should be a critical section between the Razor.Resolve and the Razor.Compile operations.

The Razor.Compile operation takes the contents of the mylayout.cshtml file and considers it with having a Model object of type IDictionary passed to it. This type should be repeated in the template file, as shown below.

Once the layout has been cached, any template file can use the layout.


The Template file - layout_test.cshtml

Here we go, then. This is the contents of my template, which is the one I want to use to fill the layout's section placeholders:
@inherits RazorEngine.Templating.TemplateBase<IDictionary<string, object>>
@{   
    this.Layout = @"mylayout";
}

@section Head
{
<head>
    <title>@Model["facebookAppID"].ToString()</title>
</head>
}

@section Body
{
<body>
my body
</body>
}
Firstly, the @inherits statement uses the generic version of RazorEngine's TemplateBase class to define the type that the @Model object will be interpreted as. This type should match whatever you are passing into the RazorEngine.Razor.Compile method when you compile the template. This should also match the type passed into the layout's compile operation.

Next, the setting of this.Layout. This is the name by which the layout's template is known within the RazorEngine cache, so it must match what was used to compile that template. Setting this string activates RazorEngine's ability to process this template while considering the contents of the layout.

After that are two @section definitions. One of which uses the @Model object which is treated as an IDictionary just as an example proving that it has access to @Model.

Note: You can also access @Model from the Layout template, without having to use the @inherit statement.

The template is compiled in much the same way, although in the Asura framework, templates are compiled on application startup and also when the last file write time is detected to be greater than that which we have recorded most recently.


Conclusion

So, long story, short, in order to get the Layout functionality working from within a .cshtml style use of RazorEngine, you have to pre-compile the templates that you will be using as a Layout before they are accessed, because RazorEngine will not do that work for you.

Hope that helps some people.

4 comments:

  1. Awesome. This really helped. I couldn't find any documentation on how to get the template/layout architecture working. Now I'm good. Thanks!

    ReplyDelete
  2. Do you have to have the same model type in the layout and email template? It kind of seems to defy the purpose of having a layout (the nice thing about razorengine to me is that i can have strongly typed models that makes sense to my view)?

    ReplyDelete
  3. Yes, I do use the same model type. I use RazorEngine in an environment where the front end views (i.e. the templates) are decoupled from the back end controllers - this isn't from a typical MVC system. The model I use is a heterogeneous collection of immutable objects and the view that they are processed by can change. The back end controllers assume nothing about the view that the model will be processed by. Therefore, in this system, it is better to make the templates very flexible as to what kind of model they are expecting. I can see that if you have a defined relationship between controller and view then a strongly typed model is an advantage, but that isn't the case with this system.

    ReplyDelete