Over a million developers have joined DZone.

How to Build a Custom View Engine with Theme Support

DZone's Guide to

How to Build a Custom View Engine with Theme Support

· ·
Free Resource
All good blogging platforms have theme support. So while working on WeBlog I initially implemented theme support by using a base controller class. The base controller class was responsible for dynamically setting the master page at runtime. I did this by assigning the action’s MasterName property in the OnActionExecuted event. Here is a short snippet of code which outlines the process.
public class BaseController
protected override void OnActionExecuted(ActionExecutedContext filterContext)
var action = filterContext.Result as ViewResult;
if (action != null)
action.MasterName = MyApp.Properties.Settings.Default.Theme;

Although the BaseController concept worked, I never liked that fact that all my other controllers had to inherit from it. As a matter of fact, when I added the BaseController class to my project I made myself an action item to research Custom View Engines as an alternative approach. In case you don’t know, the developers of ASP.NET MVC went to great lengths to make their framework completely flexible. By default, when you create a new MVC project you are using the Web Forms view engine. However, you can rip out the default view engine and register your own. As a matter of fact, there are already a variety of view engines available to us:
Since the view engines listed above are open source it was easy to find code to tailor my custom view engine after. In addition, I also found a great article titled Creating Your First MVC ViewEngine by Nick Berardi. In any case, the first step in creating a view engine is defining your search locations. If you have been working with MVC for any length of time, then you know that MVC uses a series of search paths when finding a view. So if you have a view named Index in your Post controller, MVC will look first in the Views\Post folder for the Index.aspx file. If it is not found there, then the Web Forms view engine will look in the Views\Shared folder for the view. Since we are trying to implement themes we have a few more locations that we want MVC to search in. These would be \Themes\{SomeTheme}\Views\Post and \Themes\{SomeTheme}\Shared. In order to add these new search locations we need to set the MasterLocationFormats, ViewLocationFormats and PartialLocationFormats in the constructor of our custom view engine:
public WeBlogViewEngine() : base()

base.MasterLocationFormats = new string[] {

base.ViewLocationFormats = new string[] {

base.PartialViewLocationFormats = base.ViewLocationFormats;
When looking at the code above you may initially be confused about the {2},{1} and {0}'s in the location strings. To clarify, {0} is the view name or master page name and the {1} is the controller name. However, the interesting one in our case is the "{2}" which represents a theme name. This will probably become clearer when you look at the image below which shows the directory structure used for the WeBlog project:


There are currently two themes included with WeBlog which are the Zenlike and Default theme. So the “{2}” in the location formats need to be replaced with one of these theme names at runtime. This work is initiated in the FindView method. The FindView method sets the masterName value based on the applications settings. Once the theme name is set, the value is passed to the GetPath method which eventually calls the GetPathfromGeneralName method. In the GetPathFromGeneralName method we take the format string with the “{2}” and replace it with the theme name.
private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string cacheKey, ref string[] searchedLocations)
string result = String.Empty;
searchedLocations = new string[locations.Length];

for (int i = 0; i < locations.Length; i++)
string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName, GetThemeName(controllerContext));

if (FileExists(controllerContext, virtualPath))
searchedLocations = _emptyLocations;
result = virtualPath;
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);

searchedLocations[i] = virtualPath;

return result;
So up to this point you probably think that our custom view engine is only giving us the ability to swap out our master page, right? Well, in reality we are actually doing more than this. By using a custom view engine we can also overwrite specific views in our Themes too. This works because we have multiple search locations registered in our view engine which are based on order. For example, in WeBlog I made display and editor templates for Posts which display information like the title, content, rating, tags and categories. The default templates for these items are stored in the \Shared\EditorTemplates and \Shared\DisplayTemplates folders. However, by registering the shared locations for the themes ahead of the other locations we can allow theme developers to override the base templates. Since MVC will quit looking when it finds a matching view, any view we place in our theme folder will be given precedence over the “default” views.
Registering the Custom View Engine

Registering the view engine is simple. We basically just clear out the default WebForms view engine and insert an instance of our custom view engine. This is done in the Application_Start method of the Global.asax file:
protected void Application_Start()

private static void RegisterViewEngines(ViewEngineCollection viewEngines)
viewEngines.Add(new WeBlogViewEngine());

Building a custom view engine is a great way to add theme support to an application. It allows you to have full control over the “search paths” used by MVC when it is trying to find a partial, master page or view. If you want to see an working example then just download the WeBlog source code and try it out for yourself!

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}