var self = justin();

Software Developer Teammate

A place to remind my future self of what I've learned and experienced. That means both my successes and failures.


Creating MVC Themes

During my last sprint, one of the stories we committed to was the ability to create "themes" in our site. As a team, we estimated this a relatively high point value. We thought the complexity behind trying to manage this was going to be difficult.

Custom Razor View

Turns out, we were way wrong.

public class MultiTenantViewEngine : RazorViewEngine
{
    public MultiTenantViewEngine(string templateDirectory, string themeName)
    {
        var basePath = string.Format("{0}/{1}", templateDirectory, themeName);

        AreaViewLocationFormats = new[]
        {
            basePath + "/Areas/{2}/Views/{1}/{0}.cshtml",
            basePath + "/{2}/Views/{1}/{0}.cshtml"
        };

        AreaPartialViewLocationFormats = new[]
        {
            basePath + "/Areas/{2}/Views/{1}/{0}.cshtml",
            basePath + "/{2}/Views/{1}/{0}.cshtml"
        };

        PartialViewLocationFormats = new[]
        {
            basePath + "/{1}/{0}.cshtml",
            basePath + "/{0}.cshtml",
        };

        FileExtensions = new[]
        {
            "cshtml"
        };
    } 
}

This was a quick way for us to provide "themes" in our application.

I created a custom view engine and set the locations for the engine to look for new files. The part to check out is this: basePath + "/Areas/{2}/Views/{1}/{0}.cshtml". This says to look for a file that starts in my base path, is under the Areas directory and matches the convention that MVC looks for. Position {2} is the area. {1} is the name of the controller and {0} is the view name.

This allows me to create a new folder structure underneath the base path that matches the same structure the original razor engines expects and provide a view.

Add Custom View Engine to Global.asax

In order to use the engine, I had to add to the list of available engines in the Global.asax:

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        ViewEngines.Engines.Clear();
        var basePath = ConfigurationManager.AppSettings["themeBasePath"];
        var currentTheme = //Some chosen theme;

        ViewEngines.Engines.Add(new MultiTenantViewEngine(basePath, currentTheme));
        ViewEngines.Engines.Add(new RazorViewEngine());
    }
}

You see how I clear the current list of engines? Well, the MVC engine list works first-in-first-out method (FIFO). So, the first engines I add is the first engine that is used. So why do I also add the default RazoeViewEngine? I did this so I didn't have to create a new, overridding view for every single view in the project. This way, I only have to create the overriding views for the views I actually want to override.

MVC will use my view engine first and attempt to find a view with the paths I've given it. If it can't find one in those locations, then it'll go to the next view.

Pretty simple, right?

Now, all we need to do is pull these themes out into a new project and setup our deployment process so that new themes can be created and deployed by themselves instead of doing a full code deployment.

comments powered by Disqus