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:
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" %>
- <%: 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:
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:
If we navigate to our Page One we will get the NotImplementedException
from our PageContentManager
class:
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:
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)
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 .
No new comments are allowed on this post.
Discussion
marcos
Awesome post dude! gz
Erwin Vercher
Hey man! Where can I get more posts about this?
Mau
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
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
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
Please ignore my last comment, it was really easy. It was a matter of including the whole DOM into Response.Write.
Thanks anyway.