Monday, May 16, 2011

Using Razor Templates In Your Application

We needed a templating engine that will work in a medium trust environment, after reviewing StringTemplate and NVelocity and we came to the conclusion that Razor can do all we need and more, plus it comes with the framework so no need for external dependencies.

You should be aware that Razor is compiling .NET code, it can and will create security breeches in your application if you allow users to change the templates, you can alleviate some of these security issues by segregating your code and giving the razor section access only to the parts it needs, think this through.

The project contains a few important parts.
1. It should have its own TemplatesController, its just an empty controller for the engine to run against.
2. Templates views directory, this is where we're storing the templates for the engine to execute.
3. Templates.cs is where some of the magic happens.
4. For the sake of the demo, I've added a custom WebViewPage called RazorBaseWebViewPage and a SimpleModel.


Templates.cs contains the following:
1. Execute - executes a view against a controller, with ViewData and Model.


/// <summary>
/// Generates a controller and context, hands them off to be rendered by the view engine and 
/// returns the result string
/// </summary>
/// <param name="viewName">Template Name</param>
/// <param name="model">Model for the view</param>
/// <param name="viewData">ViewData</param>
/// <returns>rendered string</returns>
private static string Execute(string viewName, ViewDataDictionary viewData, object model)
{
    var controller = new TemplatesController();
    controller.ControllerContext = new ControllerContext();
    controller.ControllerContext.HttpContext = new HttpContextWrapper(HttpContext.Current);
    controller.RouteData.DataTokens.Add("controller", "Templates");
    controller.RouteData.Values.Add("controller", "Templates");
    controller.ViewData = viewData;
    return RenderView(controller, viewName, model);
}


2. GetViewName - gets or writes a new template to the templates directory, it uses a hash of the template for the first part of the filename. Same trick as a hash table.


/// <summary>
/// Retrieves the view name by template and model
/// </summary>
private static string GetViewName(string template,Type modelType)
{
    //gets the razor template from a text template
    var razortemplate = GetViewContentFromTemplate(template, modelType);

    //gets the hash string from the razor template
    string hashstring = BitConverter.ToString(BitConverter.GetBytes(razortemplate.GetHashCode()));

    //check if view exists in folder
    var files = Directory.GetFiles(ViewDirectory, hashstring + "*.cshtml");
    foreach (var file in files)
    {
        if (File.ReadAllText(file, Encoding.UTF8) == razortemplate)
            return Path.GetFileNameWithoutExtension(file);
    }

    //if not, add it
    string filename = Path.Combine(ViewDirectory, hashstring + "_" + Guid.NewGuid().ToString() + ".cshtml");
    File.WriteAllText(filename, razortemplate,Encoding.UTF8);

    return Path.GetFileNameWithoutExtension(filename);
}


3. RenderView - calls the razor engine's Render. executed from Execute. (Origin)


/// <summary>
/// Renders a PartialView to String
/// </summary>
private static string RenderView(Controller controller, string viewName, object model)
{
    //origin http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/
    if (string.IsNullOrEmpty(viewName))
    {
        return string.Empty;
    }

    controller.ViewData.Model = model;
    try
    {
        StringBuilder sb = new StringBuilder();
        using (StringWriter sw = new StringWriter(sb))
        {
            IView viewResult = GetPartialView(controller, viewName);
            ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult, controller.ViewData, controller.TempData, sw);
            viewResult.Render(viewContext, sw);
        }
        return sb.ToString();

    }
    catch (Exception ex)
    {
        return ex.ToString();
    }
}


4. Render - the exposed method to do the actual rendering.


/// <summary>
/// Renders a template with parameters to string
/// </summary>
/// <param name="template">template text to render</param>
/// <param name="model">the model to give the template</param>
/// <param name="parameters">the ViewData for the execution</param>
/// <returns>rendered template</returns>
public static string Render(string template, object model, ViewDataDictionary parameters)
{
    //if empty
    if (string.IsNullOrEmpty(template))
        return string.Empty;

    //if doesn't contain razor code
    if (template.IndexOf("@") == -1)
        return template;

    //get View filename
    string fileName = GetViewName(template, (model != null) ? model.GetType() : typeof(object));

    //Execute template
    return Execute(fileName, parameters, model);
}



I've ran some analysis on the code's performance, a few places might be helpful to optimize is the GetPartialView and GetViewName are slow.

You can find the project here:
https://github.com/drorgl/ForBlog/tree/master/RazorTemplateDemo