RazorEngine layouts

I am using the Razor engine https://github.com/Antaris/RazorEngine to parse the body of my email templates. Is it possible to define a layout and include other .cshtml files? for example a common header and a footer.


I got common templates and a layout working, with the help of these two posts:

RazorEngine string layouts and sections?

http://blogs.msdn.com/b/hongyes/archive/2012/03/12/using-razor-template-engine-in-web-api-self-host-application.aspx

This is my solution:

Solution 1: Layout

Used by setting _Layout

@{
    _Layout = "Layout.cshtml";
    ViewBag.Title = Model.Title;
 }

Footer

@section Footer 
{
   @RenderPart("Footer.cshtml")
}

Layout.cshtml

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>
    <head>
    </head>
    <body>
        <div id="content">
            @RenderBody()
        </div> 
        @if (IsSectionDefined("Footer"))
        { 
            <div id="footer">
                @RenderSection("Footer")
            </div>
        }
    </body> 
</html>

TemplateBaseExtensions

Extend TemplateBase with a RenderPart Method

public abstract class TemplateBaseExtensions<T> : TemplateBase<T>
{
    public string RenderPart(string templateName, object model = null)
    {
        string path = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates", templateName);
        return Razor.Parse(File.ReadAllText(path), model);
    }
}

Razor Config

Set BaseTemplateType to your TemplateBaseExtensions class

TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration
{
     BaseTemplateType = typeof(TemplateBaseExtensions<>)
};

Razor.SetTemplateService(new TemplateService(templateConfig));

Edit Solution 2:

If you are using a TemplateResolver. RenderPart isn't needed use the @Include instead

Footer

@section Footer 
{
   @Include("Footer.cshtml")
}

Resolver

public class TemplateResolver : ITemplateResolver
{
    public string Resolve(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        string path = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Templates", name);
        return File.ReadAllText(path, System.Text.Encoding.Default);
    }
}

Config

TemplateServiceConfiguration templateConfig = new TemplateServiceConfiguration
{
     Resolver = new TemplateResolver()
};
Razor.SetTemplateService(new TemplateService(templateConfig));

Update by The Muffin Man Specify a template and render a string

var templateResolver = Razor.Resolve("Registration.cshtml");
return templateResolver.Run(new ExecuteContext());

Also I, along with others at this link https://github.com/Antaris/RazorEngine/issues/61 had issues with using _Layout whereas Layout worked.

'_Layout' is the old syntax. It was updated to 'Layout' in a future release.


You can easily do a lot of stuff with Razor; however, that particular project seems to abstract away a lot of the Razor engine stuff you could do (which is both good and bad). In your situation, it sounds like you would be much better off implementing your own Razor solution (it's actually not that bad) and then you can have your templates throw exceptions or pull in other content pretty easily.

For example; rolling your own solution allows you to make a base class for your razor templates which can expose the capacity to pull in "partial views" by invoking other templates. In addition, you can do model checking and throw exceptions if certain properties are null.


The easiest way to implement a layout with RazorEngine is by replacing what your template return in the @RenderBody() of the layout:

 var finalHtml = layout.Replace(@"@RenderBody()", templateHtml);

E.g.:

Your _Layout.cshtml with the typical @RenderBody()

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html>
    <head>
    </head>
    <body>
        <div>
            @RenderBody()
        </div> 
    </body> 
</html>

Your RazorEngine template MyTemplate.cshtml

@using RazorEngine.Templating
@inherits TemplateBase<myviewmodel>

<h1>Hello People</h1>
<p>@Model</p>

And wherever you call the RazorEngine template:

var TemplateFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "EmailTemplates");
var template = File.ReadAllText(Path.Combine(TemplateFolderPath,"MyTemplate.cshtml"));
var layout = File.ReadAllText(Path.Combine(TemplateFolderPath, "_Layout.cshtml"));
var templateService = new TemplateService();
var templateHtml = templateService.Parse(template, myModel, null, null);
var finalHtml = layout.Replace(@"@RenderBody()", templateHtml);