Update 2009/03/26: There is an updated version of this code (with improvements) that targets the ASP.NET MVC 1.0 RTW located here: /post/2009/03/ASPNET-MVC-Implement-Theme-Folders-using-a-Custom-ViewEngine.aspx

About two weeks ago I posted on how to Implement Custom Theme support in ASP.NET MVC. There were some breaking changes made when the Preview 5 release was released yesterday.

Here’s a short list to a couple of the changes I had to make to my code from the previous post to get it working in ASP.NET MVC Preview 5:

  • Delete WebFormThemeViewLocator - The contents of this object is now contained within the ViewEngine itself
  • Delete WebFormThemeControllerFactory- This isn't needed anymore.
  • Modify WebFormThemeViewEngine - Write a bunch of code that finds the appropriate View to use.
  • Modify Global.asax - Remove code that adds the old ControllerFactory, and replace it with code that adds our newly improved WebFormThemeViewEngine
  • Modify ControllerBase - Firstly, rename this to ThemeControllerBase since there is not a ControllerBase in System.Web.Mvc. Then, modify the code for the Execute method since it now takes in a RequestContext object as a parameter instead of a ControllerContext object.

Just for reference here’s the code for the WebFormThemeViewEngine.

Download Code: ASPNETMVC_Preview5_CustomThemeImplementation.zip (226.05 kb)

Below is the entire code for the WebFormThemeViewEngine, just for reference. If you are interested in looking at how I implemented this, just download and check out the entire code sample at the link above.

using System;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;

public class WebFormThemeViewEngine : System.Web.Mvc.WebFormViewEngine
{
    public WebFormThemeViewEngine()
    {
        base.ViewLocationFormats = new string[] {
                "~/Views/{2}/{1}/{0}.aspx",
                "~/Views/{2}/{1}/{0}.ascx",
                "~/Views/{2}/Shared/{0}.aspx",
                "~/Views/{2}/Shared/{0}.ascx"
            };

        base.MasterLocationFormats = new string[] {
                "~/Views/{2}/{1}/{0}.master",
                "~/Views/{2}/Shared/{0}.master"
            };

        base.PartialViewLocationFormats = new string[] {
                "~/Views/{2}/{1}/{0}.aspx",
                "~/Views/{2}/{1}/{0}.ascx",
                "~/Views/{2}/Shared/{0}.aspx",
                "~/Views/{2}/Shared/{0}.ascx"
            };
    }

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(viewName))
        {
            throw new ArgumentException("Value is required.", "viewName");
        }

        string themeName = this.GetThemeToUse(controllerContext);

        string[] searchedViewLocations;
        string[] searchedMasterLocations;

        string controllerName = controllerContext.RouteData.GetRequiredString("controller");

        string viewPath = this.GetPath(this.ViewLocationFormats, viewName, controllerName, themeName, out searchedViewLocations);
        string masterPath = this.GetPath(this.MasterLocationFormats, viewName, controllerName, themeName, out searchedMasterLocations);

        if (!(string.IsNullOrEmpty(viewPath)) && (!(masterPath == string.Empty) || string.IsNullOrEmpty(masterName)))
        {
            return new ViewEngineResult(this.CreateView(controllerContext, viewPath, masterPath));
        }
        return new ViewEngineResult(searchedViewLocations.Union(searchedMasterLocations));
    }

    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentException("Value is required.", partialViewName);
        }

        string themeName = this.GetThemeToUse(controllerContext);

        string[] searchedLocations;

        string controllerName = controllerContext.RouteData.GetRequiredString("controller");

        string partialPath = this.GetPath(this.PartialViewLocationFormats, partialViewName, controllerName, themeName, out searchedLocations);

        if (string.IsNullOrEmpty(partialPath))
        {
            return new ViewEngineResult(searchedLocations);
        }
        return new ViewEngineResult(this.CreatePartialView(controllerContext, partialPath));
    }

    private string GetThemeToUse(ControllerContext controllerContext)
    {
        string themeName = controllerContext.HttpContext.Items["themeName"] as string;
        if (themeName == null) themeName = "Default";
        return themeName;
    }

    private string GetPath(string[] locations, string viewName, string controllerName, string themeName, out string[] searchedLocations)
    {
        string path = null;

        searchedLocations = new string[locations.Length];

        for (int i = 0; i < locations.Length; i++)
        {
            path = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { viewName, controllerName, themeName });
            if (this.VirtualPathProvider.FileExists(path))
            {
                searchedLocations = new string[0];
                return path;
            }
            searchedLocations[i] = path;
        }
        return null;
    }
}</pre>