Razor and ASP.Net
As a normal person you’d probably be happy with how Razor template files are used within MVC; there’s a nice convention for where they live – they’ll be in a Views folder within your project most likely – and you refer to them either by name or sometimes just by convention – what’s that? You have an ActionResult method called “Index”? I’ll go fetch the “Index” view from the folders I normally expect the cshtml files to live in for ya then.
The way this works is fantastic; development can steam ahead without the pain and confusion of all of the possible ways you could do it wrong when choosing webforms and .aspx files.
Of course, the MS implementation of an MVC framework in itself is a wonderful thing; all but enforcing the separation of concerns that is just so easy to ignore in webforms.
Razor outside of ASP.Net
But what about when you want to dynamically generate html without a process being hosted as a website? One big use case for this is email generation; sure, you could host an MVC web API and have the content generation process constantly call it, but that seems a little inefficient.
RazorEngine and RazorMachine
There are a few solutions to this; you can actually hand roll your own (I might get onto that in a future post) or you can try out some reasonably well known open source solutions like RazorEngine:
A templating engine built on Microsoft’s Razor parsing engine, RazorEngine allows you to use Razor syntax to build dynamic templates:
[csharp]string template = "Hello @Model.Name, welcome to RazorEngine!";
string result = Razor.Parse(template, new { Name = "World" });
[/csharp]
and RazorMachine:
RazorMachine is a robust and easy to use .Net Razor v2/v3 template engine. The master branch uses Razor v3. This implementation supports layouts (masterpages) and a _viewStart construct, just like MVC does support these features. The RazorEngine works independently from MVC. It only needs the System.Web.Razor reference. It almost works exacly like Asp.Net MVC
[csharp]var rm = new RazorMachine();
var result =
rm.Execute("Hello @Model.FirstName @Model.LastName", new {FirstName="John", LastName="Smith"});
[/csharp]
There’s a short stackoverflow answer comparing them (and RazorTemplates, another similar OSS solution) too.
Getting stuck in
Create a new project and use some nuget awesomeness
[powershell]Install-Package razormachine[/powershell]
Then, if you don’t already, add references to
[csharp]system.web.helpers // for json.decode
microsoft.csharp // for dynamic types
[/csharp]
If you want to debug this functionality via a console app running from VisualStudio, you may need to uncheck “enable visual studio hosting process” in Project -> Properties -> Debug
If you want to run this outside of Visual Studio, you can just run the compiled exe (bin/debug) as admin.
If you’re using a test runner then you might be fine as is. I can’t actually remember the issue I was having as I now can’t recreate it, but I think it might have been around using dynamic models and Json decoding.
RazorEngine
This is the core bit of functionality for a basic use case for RazorEngine:
[csharp]var model = Json.Decode("{\"Description\":\"Hello World\"}");
var template = "<div class=\"helloworld\">@Model.Description</div>";
const string layout = "<html><body>@RenderBody()</body></html>";
template = string.Format("{0}{1}", "@{Layout=\"_layout\";}", template);
using (var service = new TemplateService())
{
service.GetTemplate(layout, null, "_layout");
service.GetTemplate(template, model, "template");
var result = service.Parse(template, model, null, "page");
Console.Write(result);
Console.ReadKey();
}
[/csharp]
Your output should be:
[csharp]<html><body><div class="helloworld">Hello World</div></body></html>
[/csharp]
Pretty easy, right?
RazorMachine
Here’s the equivalent in RazorMachine
[csharp]var model = Json.Decode("{\"Description\":\"Hello World\"}");
var template = "<div class=\"helloworld\">@Model.Description</div>";
const string layout = "<html><body>@RenderBody()</body></html>";
var rm = new RazorMachine();
rm.RegisterTemplate("~/shared/_layout.cshtml", layout);
var renderedContent =
rm.ExecuteContent(string.Format("{0}{1}", "@{Layout=\"_layout\";}", template), model);
var result = renderedContent.Result;
Console.Write(result);
Console.ReadKey();
[/csharp]
Again, same output:
[html]<html><body><div class="helloworld">Hello World</div></body></html>
[/html]
Notice that in both of them you have to lie about there being a layout file existing somewhere; in RazorEngine you give it a name:
[csharp]template = string.Format("{0}{1}", "@{Layout=\"_layout\";}", template);[/csharp]
then refer to that name when adding the template:
[csharp]service.GetTemplate(layout, null, "_layout");[/csharp]
In RazorMachine you register the template as a dummy virtual file:
[csharp]rm.RegisterTemplate("~/shared/_layout.cshtml", layout);[/csharp]
then refer back to it as you would normally do within ASP.Net MVC when executing the content:
[csharp]var renderedContent =
rm.ExecuteContent(string.Format("{0}{1}", "@{Layout=\"_layout\";}", template), model);[/csharp]
Differences
I’ve found it easier to process sub templates (such as @Include) within RazorEngine, as I just recursively scan a file for that keyword and add the corresponding template to the service, e.g. look at the ProcessContent and ProcessSubContent methods below:
[csharp]public class RenderHtmlPage
{
private readonly IContentRepository _contentRepository;
private readonly IDataRepository _dataRepository;
public RenderHtmlPage(IContentRepository contentRepository,
IDataRepository dataRepository)
{
_contentRepository = contentRepository;
_dataRepository = dataRepository;
}
public string BuildContentResult(string page, string id)
{
using (var service = new TemplateService())
{
// get the top level razor template, e.g. "product"
// equivalent of "product.cshtml"
var content = GetContent(page);
var data = GetData(id);
ProcessContent(content, service, data);
var result = service.Parse(content, data, null, page);
return result;
}
}
private void ProcessContent(string content,
TemplateService service,
dynamic model)
{
// does the string passed in reference a Layout at the start?
const string layoutPattern = @"@\{Layout = ""([a-zA-Z]*)"";\}";
// does the string passed in reference an Include anywhere?
const string includePattern = @"@Include\(""([a-zA-Z]*)""\)";
// recursively process the Layout
foreach (Match match in Regex.Matches(content, layoutPattern,
RegexOptions.IgnoreCase))
{
ProcessSubContent(service, match, model);
}
// recursively process the @Includes
foreach (Match match in Regex.Matches(content, includePattern,
RegexOptions.IgnoreCase))
{
ProcessSubContent(service, match, model);
}
}
private void ProcessSubContent(TemplateService service,
Match match,
dynamic model)
{
var subName = match.Groups[1].Value; // got an include/layout match?
var subContent = GetContent(subName); // go get that template then
ProcessContent(subContent, service, model); // recursively process it
service.GetTemplate(subContent, model, subName); // add it to the service
}
private string GetContent(string templateToLoad)
{
// hit the filesystem, db, API, etc to retrieve the razor template
return _contentRepository.GetContent(templateToLoad);
}
private dynamic GetData(string dataToLoad)
{
// hit the filesystem, db, API, etc to return some Json data as the model
return Json.Decode(_dataRepository.GetData(dataToLoad));
}
}
[/csharp]
Why is this useful?
I’m not going to go into the details of either RazorMachine or RazorEngine; there’s plenty of documentation up on their respective websites already. I’ve used @Includes in the examples above due to its simplicity; the libraries have differing support for things like @Html.Partial and also can be extended.
Unfortunately, the html helpers (like @Html.Partial) need to have an HttpContext and run inside of ASP.Net MVC; which is what I’m trying to avoid for now.
If you pull down my initial teeny solution from github and look at the tests you’ll notice the content of the template, layout, and model are either strings or coming from the filesystem; not related to the structure of the project or files in the project or anything like that.
This means we can deploy a rendering process that returns rendered html based on strings being passed to it. Let’s play with this concept a bit more.
Flat File Web Page Generation
Say you wanted to “host” a website directly within a CDN/cache, thus avoiding the hosting via the normal route of servers and related infrastructure. Sure, writing flat html in a text editor is a solution, but what if you wanted to still be able to structure your pages into common modules, write C# code to manage the logic to dynamically combine them, and use Razor syntax and views for defining the front end?
This next section plays on this concept a bit more; we’ll write a small app that accesses a couple of directories – one for Razor files, one for data files – and generates a flat website into a third directory.
I will then expand on this concept over a series of posts, to make something more realistic and potentially useful.
Command Line & FileSystem FTW
I’ve created another repo up on github for this section, but cutting to the chase – here is the guts of demo console app:
[csharp]const string workingRoot = "../../files";
IContentRepository content =
new FileSystemContentRepository(workingRoot + "/content");
IDataRepository data =
new FileSystemDataRepository(workingRoot + "/data");
IUploader uploader =
new FileSystemUploader(workingRoot + "/output");
var productIds = new[] {"1", "2", "3", "4", "5"};
var renderer = new RenderHtmlPage(content, data);
foreach (var productId in productIds)
{
var result = renderer.BuildContentResult("product", productId);
uploader.SaveContentToLocation(result, productId);
}
[/csharp]
The various FileSystemXX implementations either just read or write files from/to the file system. Natch.
So what we’ve got here is an implementation of the RazorEngine methods I pasted in above wrapped in a RenderHtmlPage class, being called for a number of “productIds”; these happen to exist as json files on disc, e.g. “1.json”.
Each file is being combined with whatever Razor templates are listed in the product cshtml file and its referenced @Includes. The resulting html is then saved back to the file system.
So with these views in files/content:
And these json files in files/data:
We get these html files generated in files/output:
Hopefully you can see where this is leading; we can keep Views in one place, get the model data from somewhere else, and have the extremely generic rendering logic in another place.
The Theory
With this initial version we could take an existing ASP.Net MVC website (assuming it didn’t use any html helpers in the views..) and process it offline with a known dataset to create a readonly version of the website, ready to serve from a filesystem.
Next Up
I’ll take this concept and run with it across various implementations, gradually ending up on something that might even be useful!
What’s up, I have noticed that on occasion this site shows an 403 server error message. I figured you would like to know. Regards
Thanks! I’ve noticed a few outages; looks like an Amazon Micro EC2 instance isn’t going to cut it for a WP install after all. I’ve just migrated over to Digital Ocean (and their SSD epic-ness), so let’s see how that goes..