Build dynamic content templates using the Razor syntax

Posted on March 21st, 2011

Imagine, if you will, that you are tasked with building a content management system that supports tokens for inserting dynamic content. Easy, right? Just decide on a token syntax that you want to use (maybe surrounding names with double brackets like [[CompanyName]]) and then write some classes that leverage Regex to look for your list of tokens and replace with your content. Sure, that is one way...if you want to get your early 2000's on. But this is 2011 and we have Razor, so lets see if we can use that. Then we can write all of our template content in Razor, build a view model for our templates that can be populated with our dynamic content, and let Razor do all the content replacement for us (3 cheers for not having to write any regular expressions).

Step 1: The set up

Begin by creating a new MVC 3 website. I am going to use the ASPX view engine just to make it a bit easier for us to focus on which content is our template content and which is our website static content, but we could certainly pick the Razor view engine here as well:

alt text

In our content management system we would have a database layer for handling user authentication and all the tools for maintaining our content. Since we are going to focus on using the Razor engine for rendering our dynamic content we will skip over the database layer and tools and just write enough code to get us to the point of sending some stored content through our Razor engine pipeline that we will build and render it out.

We need to set up a controller and view for rendering our dynamic content. Lets assume we want to be able to create dynamic pages. We will create a controller named PageController and add an action method called Render that will take in a pageName and render the content for that page. We will populate the method with an instantiation of a PageContentManager class that will handle working with our dynamic page data and make a call to a method named GetPageContent (we will dig into the logic for this class in Step 2). Then we will write that content to the Response stream and return null from our action method since we will not be using the MVC engine to render a view.

using System.Web.Mvc;
using Website.Models;

namespace Website.Controllers
{
    public class PageController : Controller
    {
        public ActionResult Render(string pageName)
        {
            var manager = new PageContentManager();
            var content = manager.GetPageContent(pageName);
            Response.Write(content);
            return null;
        }
    }
}

We need to add a route in our RegisterRoutes method found in the Global.asax to get to our controller and action:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "DynamicPage",
            "Page/Render/{pageName}",
            new { controller = "Page", action = "Render", pageName = "" }
        );

        routes.MapRoute(
            "Default",
            "{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }

Lets also create a simple site shell and homepage so we can navigate around. We need to add a Site.Master to our Views/Shared directory and give it some content (including 2 links to some content pages we will build):

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>



    <asp:ContentPlaceHolder ID="TitleContent" runat="server" />


    
  • <%: Html.ActionLink("Home", "Index", "Home") %>
  • <%: Html.ActionLink("Page One", "Render", new { Controller="Page", pageName = "Page-One" })%>
  • <%: Html.ActionLink("Page Two", "Render", new { Controller = "Page", pageName = "Page-Two" })%>

We need to create our HomeController class and add an Index action method:

using System.Web.Mvc;

namespace Website.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

...as well as an Index view in Views/Home:

<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %>
Home

    

Welcome to our site with dynamic content!

Our solution tree with all of our files for step 1:

alt text

There are many other ways that we can handle getting and rendering our dynamic content within the MVC engine, but that is not the focus of this article so we won't concern ourselves with that for now. On to Step 2 and a look at our PageContentManager code.

Step 2: The content generation engine

If we set up the minimal amount for our PageContentManager class as follows:

using System;

namespace Website.Models
{
    public class PageContentManager
    {
        public string GetPageContent(string pageName)
        {
            throw new NotImplementedException();
        }
    }
}

...we can then F5 our application and do a quick run through our links to verify that our goods from Step 1 are all in order:

alt text

If we navigate to our Page One we will get the NotImplementedException from our PageContentManager class:

alt text

Time to fill in our template management code and replace that exception with some content. The requirements for extending the Razor view engine compiler outside of being directly coupled to MVC is fairly extensive (we are really just concerned with parsing some text and a model and getting text back, so technically we don't need to concern ourselves with any MVC specific logic), but luckily there is a kick ass CodePlex project named that is available to handle all of the heavy lifting so we can just concern ourselves with passing in some template code and a model and getting a string of compiled and parsed code back. Even more kick ass is the fact that we can pull this puppy down using (if you aren't using NuGet yet you can get instructions on how to set up the extension in Visual Studio via the ). A call to the following command in the NuGet Package Manager Console in Visual Studio will install everything you need for the RazorEngine code:

Install-Package RazorEngine

This will add the necessary References to your web project. It looks like the install adds the compiled System.Web.Razor.dll library and some required Microsoft class libraries that it needs to reference. The RazorEngine library will show up as RazorEngine in your list of References in your project and will already have the Copy Local value set to true, so the dll will get delivered when you are ready to deploy your web application to a server.

Lets update our PageContentManager class to instantiate a standard view model, get our dynamic page content, and run it through the RazorEngine code to produce our dynamic content. First we will create the standard view model class that we will call StandardContentViewModel. We will be using this class to represent all of our possible token content for a standard content page. For now we will add a property to it to contain a company name. The class will look like so:

namespace Website.Models
{
    public class StandardContentViewModel
    {
        public string CompanyName { get; set; }
    }
}

Next we want to update our PageContentManager class to instantiate an instance of our view model with content, get our template content based on the page name, and run it through the RazorEngine. We will create a private method for getting our page content based on page name and use a simple if/else statement to return our page specific content.

using RazorEngine;

namespace Website.Models
{
    public class PageContentManager
    {
        public string GetPageContent(string pageName)
        {
            var templateContent = this.getPageContentFromDataSource(pageName);
            var standardContentViewModel = new StandardContentViewModel { CompanyName = "ABC Corp" };
            var content = Razor.Parse(templateContent, standardContentViewModel);
            return content;
        }

        private string getPageContentFromDataSource(string pageName)
        {
            if (pageName == "Page-One")
                return "This is the first page for @Model.CompanyName. What do you think?";
            return @"@Model.CompanyName is back for more with their second page 
                of @Model.CompanyName related content.";
        }
    }
}

Note that we are not concerned about how we populate our view model data and how we lookup and retrieve our page content within the scope of this article. Hence the rudimentary hard coded logic above. If we expanded the scope we would be looking at handling the view model instantiation by some sort of data layer call to get our dynamic fields with some way to cache or limit any re-query of data within a single action request so we could call GetPageContent to collect pieces of page content to stitch together a full page using partial views. We would also hit our data source for our page content and add some exception throwing that our PageController could catch and identify scenarios such as "Page Not Found" for non existent page names and deliver correct HTTP codes (like 404) to the client, etc.

Now, if we F5 our application, we can navigate to our Page One and Page Two content pages and see our template content run through the RazorEngine washer and spit out to our browser with the appropriate dynamic content applied:

alt text

alt text

Notice that we don't have any top level nav in our content pages. That is because our PageController action method Render is simply writing the compiled and parsed template page code directly to the Response stream and not using the MVC view engine to render any view structure. This illustrates how we could leverage this same code outside of an MVC application (provided we reference the correct class libraries) or use the code to generate something like email content that our application may want to deliver (simply stuff the returned value of Razor.Parse into the body of an email message object). But since we are tasked with building a content management system for a web application we would like to be able to render this page content within the shell of our site. Easy enough. Lets add a view named Render.asxp in a Views/Page dir, fill it with some content

<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" 
    MasterPageFile="~/Views/Shared/Site.Master" %>


    <%=Model%>

...and then return the view from our Render action method

using System.Web.Mvc;
using Website.Models;

namespace Website.Controllers
{
    public class PageController : Controller
    {
        public ActionResult Render(string pageName)
        {
            var manager = new PageContentManager();
            var content = manager.GetPageContent(pageName);
            this.ViewData.Model = content;
            return View();
        }
    }
}

Note that our Render.aspx view is a strongly typed view with string as the model type and our call to render the model, <%=Model%>, is not using the syntax to run the variable content through the MVC html encoding logic. We are doing this because we want our dynamic page content to be able to contain html tags within it. Lets add a html tag to our Page One content back in our PageContentManager class real quick and then we can F5 to see that our Render action method is now using our view that references our Site.Master.

using RazorEngine;

namespace Website.Models
{
    public class PageContentManager
    {
        public string GetPageContent(string pageName)
        {
            var templateContent = this.getPageContentFromDataSource(pageName);
            var standardContentViewModel = new StandardContentViewModel { CompanyName = "ABC Corp" };
            var content = Razor.Parse(templateContent, standardContentViewModel);
            return content;
        }

        private string getPageContentFromDataSource(string pageName)
        {
            if (pageName == "Page-One")
                return "This is the first page for @Model.CompanyName. What do you think?";
            return @"@Model.CompanyName is back for more with their second page 
                of @Model.CompanyName related content.";
        }
    }
}

(we added the tag around the @Model.CompanyName string in the return value for the match on the "Page-One" pageName string)

alt text

alt text

Rock on. Now we are dialed in. We have created a base framework for leveraging a content template engine that can parse string content and replace tokens based on the Razor syntax. Now we don't have to worry about writing and maintaining regular expression or transformation code to handle replacing tokens (or even inline logic like loops, if/else, etc) with dynamic data in our page content. All we need to do is maintain our view model that our page(s) support and make sure the people using the content editor are using valid token syntax (which is easier said than done, but this is something we could accomplish in the UX by providing buttons to add pre-defined content that would inject the proper token syntax like @Model.CompanyName into our page content editor).

While using this template method provides a bunch of advantages, it isn't bulletproof. Your page content will be run through a compiler, so if you have any syntax issues (for example, your content contains the string @schwarty to represent a twitter handle) the compiler will throw an exception. This is a Razor syntax issue and can be solved by learning the basics of the syntax ( is a great post on it), however that doesn't really solve the real-world use case scenario of potentially having someone who is not a programmer needing to maintain content in your content management system. So you would find yourself needing to cleanse your incoming page content data to make sure literal text is properly escaped, which may lead you right back to those regular expressions that we were so cheerful about eliminating. Is it worth it? Well, I guess that is a "right tool for the job" type of discussion...not in our scope of this article. However, if you wanted to know how to leverage the Razor engine in a content template type scenario then now you know! Have fun, and show some love to the crew that made RazorEngine and NuGet if you end up using them.

Notes and Credits

  • It looks like there may be some additional steps to be able to leverage RazorEngine in Medium Trust. You can read more .
  • The RazorEngine project on CodePlex can be found .
  • NuGet and all its awesomeness can be found .

Discussion

marcos
marcos
27 Sep, 2011 09:58 PM

Awesome post dude! gz

Erwin Vercher
Erwin Vercher
17 Nov, 2011 04:50 PM

Hey man! Where can I get more posts about this?

Mau
Mau
12 Mar, 2012 05:36 AM

You have made my day. This is exactly what I was trying to accomplish for setting a reporting framework using Razor. Absolutly brilliant and simple. Thanks.

Jeff
Jeff
04 Apr, 2012 04:07 AM

I am interested in expanding my existing MVC3 site to include a CMS-like controller for Operations and Marketing to modify text and images. Are you aware of such a solution, or have you accomplished dynamic content templates to include images?

Thanks

Jeff in Seattle, jeff00seattle

Mau
Mau
06 Apr, 2012 06:50 AM

Hi Justin, I have been playing a lot with this idea of dynamic content for a project and the only thing I can't figure it out is how to attach the CSS links.

I have tried adding headers to the Response before the Response.Write but it does not behave like I would expect, as it just output the string there.

Do you have any clue how can this be achieved? Thanks.

Mau
Mau
06 Apr, 2012 07:09 AM

Please ignore my last comment, it was really easy. It was a matter of including the whole DOM into Response.Write.

Thanks anyway.

No new comments are allowed on this post.