Posterous theme by Cory Watilo

Adding support for skins in an ASP.NET MVC application

This post will show the steps you need to take in order to add skin support into your ASP.NET MVC applications. The full source code can be located at the end of the post. Let’s get started, shall we?


Templates, skins and themes


These three words are quite often used interchangeably on the web so I thought it would be best to provide the definition I will be using throughout the context of this post, to avoid the risk of confusion.


Template

A pattern which determines how a and item should be represented in our view. For a list of items one template could output an unordered list while another template could output a series of divs or a table.


Skin

The visual representation of how the elements, generated by a template, should be displayed to the view. In the world of ASP.NET (MVC) this is the master page, which in turn can load different style sheets, images and resources.


Theme

A skin can have one or many different themes, for example you could provide a summer and a winter theme to your skin. The easiest way to implement themes is to load different style sheets into your skin.



Defining our goals


What we want to be able to do is to alter the appearance of our views by dynamically applying different skins (master pages) to our views, based on some sort of parameter value. This configuration could be a query string parameter, a key in the web.config file or perhaps a unique value stored in the visitors profile and so on (choose what suits your needs). For the sake of the simplicity, and to enable to illustrate swapping skins at runtime, we will be using a query string parameter.


We want to support multiple skins, each with its own set of images, stylesheets, javascripts and what ever resources you want to use. Based on this we can decide that the best option will be to create a skins folder and use a unique sub-folder for each skin, where the name of the folder is the name of the skin.


Now we know what we want to do, what’s next? We need to figure out an approach to dynamically apply the skin to a view when it’s requested.


Figuring out where to apply our monkey patch


The first thing we need to figure out is how to make the right skin be applied to a view when it’s displayed, and in order to do that we need to have a bit of understanding of how the ASP.NET MVC framework works with views.


We know that we want to alter the appearance of a view, so a good place to start looking would be the place that serves the views back to the client; the controllers, or more specifically the action methods. Have you ever studied the signature and pattern of an action method?


public ActionResult Index(){    return View();}

By looking at the (textbook example) action method above we see that it doesn’t really serve a view, but an ActionResult object. But aren’t controllers suppose to return views? Yes they should, and they do.


The action results are a nice level of abstraction introduced by the ASP.NET MVC team in the Preview 3 bits, enabling you to serve different types of responses to the client. The class itself is an abstract class with a single method (ExecuteResult). The framework is shipped with a set of classes which inherits this class and provides functionality and you are free to derive your own class to provide custom functionality.


The second part of the action method is the View helper method which is defined on the Controller class. This method creates, and populates, a new ViewResult (which inherits ActionResult) object and it’s inside this object that the real magic takes place.


When the ExecuteResult method is invoked (the is a class in the framework called ControllerActionInvoker which figures out which action method to call for an incoming request and then executes the returned action result) on the ViewResult object it starts to figure out exactly what view it should render.


Even though the framework render webform views (.aspx and .ascx) out-of-the-box does not mean you are limited to this. The ASP.NET team has taken great effort to make the framework as extendable (and testable) as possible. The inner workings of the framework uses the notation of view engines to render a view. You can implement you own view engines by implementing the IViewEngine interface and there are already many implementations freely available to use.


So wouldn’t it be great if the webform view engine in the framework could be used to inject the right master page into a view when it’s being rendered? If your answer is yes then continue reading, if not then then you should continue reading anyway and give me a chance to persuade you!


Implementing a custom view engine


Unfortunately the default view engine does not let us dynamically change the master page so we’re going to have to implement our custom engine and tell the framework to use is instead.


The logical place to start would be to create a new class and implement the IViewEngine interface and provide code for the two methods it defines. Two methods doesn’t sound too hard but we would also have to write a lot of other plumbing code, something we should try and avoid if we can and reuse proven code. The default view engine for webform view is called WebFormViewEngine and upon inspection you’ll notice that it does not implement the IViewEngine interface directly but instead it derives from class called VirtualPathProviderViewEngine, which in turn implements the interface and provides most of the plumbing code we need.


So should we derive our own engine from the VirtualPathProviderViewEngine class? We could but we’re better of deriving from the WebFormViewEngine, extend its functionality to suit our needs, and take advantage of the webform specific modifications it adds (means we have to write even less code to achieve our goal).

Fortunately one of the principles, used by the ASP.NET MVC team, behind the framework is to not seal a class unless you have very good reason to and in this case there’s no such reason and we’re free to derive and extend as much as we want!


So let’s get our hands dirty and start writing some code.


Creating the project and setting the stage


Launch Visual Studio and create a new ASP.NET MVC Web Application project and give it a suitable name, I will use CodeJunkie.Experimental.Mvc.


Next create a new folder, in the root of the project, called Skins and create two sub-folders called Default and Red. These will be our two skins for this example. We will be adding all skin specific contents into these two folders but there is nothing preventing you from creating what ever folder structure you want inside of your these folder, to help out organize items.


The next couple of steps are just as easy, but they require you to pay attention with some renaming or you will get an exception when you try to execute the project.


Locate the Site.css file in the ~/Contents folder and copy it to both the Default and Red skin folder. Next locate the Site.master file located in the ~/Views/Shared folder and copy it to both skin folders as well.


Once you are one with that you can remove the entire ~/Contents folder and also delete the Site.master file in the ~/Views/Shared folder, leaving you with the following folder structure in your project.


Media_httplh4ggphtcom_qzhbi


Next open the Site.css file in the ~/Skins/Red folder and edit the background-color tag to have the value #dc5f5d


body{    background-color: #dc5f5d;    font-size: .75em;    font-family: Verdana, Helvetica, Sans-Serif;    margin: 0;    padding: 0;    color: #696969;}

You’ll also need to open the Site.master code behind file in the Default skin folder and update the namespace to CodeJunkie.Experimental.Mvc.Skins.Default


namespace CodeJunkie.Experimental.Mvc.Skins.Default{    public partial class Site : System.Web.Mvc.ViewMasterPage    {    }}

As well as to change the markup file so that it inherits from the correct file


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs"    Inherits="CodeJunkie.Experimental.Mvc.Skins.Default.Site" %>

Repeat the two steps for the Site.master file in the Red skin folder but instead use the namespace CodeJunkie.Experimental.Mvc.Skins.Red


These steps will make sure that we have two unique master pages set up. Next add a new class in the root of the project, give it the name WebFormSkinViewEngine and have it derive from the WebFormViewEngine class, found in the System.Web.Mvc namespace.


public class WebFormSkinViewEngine : WebFormViewEngine{}

View engines which derive from the VirtualPathProviderViewEngine uses three string arrays internally to know where to look for various items. Their names are MasterLocationFormats (for master pages), ViewLocationFormats (for views) and PartialViewLocationFormats (for partial views). The values of these arrays are format strings, i.e. they contain placeholders which are filled with the controller name and the view name, using the string.format method.


The default locations for the WebFormEngine class are


MasterLocationFormats = new[] {    "~/Views/{1}/{0}.master",    "~/Views/Shared/{0}.master"};ViewLocationFormats = new[] {    "~/Views/{1}/{0}.aspx",    "~/Views/{1}/{0}.ascx",    "~/Views/Shared/{0}.aspx",    "~/Views/Shared/{0}.ascx"};PartialViewLocationFormats = ViewLocationFormats;

The {0} parameter is replaced with the view name and the {1} placeholder is replaced with the name of the controller. We want to add some custom logic concerning the lookup of the master pages and since we know that the location of the Site.master file will change depending on which skin is being requested, we need to modify the values of the MasterLocationFormats array in our own engine.


Add the following constructor into the WebFormSkinViewEngine class.


public WebFormSkinViewEngine(){    this.MasterLocationFormats = new []    {        "~/Skins/{2}/Site.master"    };}

What we’ve done here is introduce the {2} parameter which will be replaced with the name of our skin, thus dynamically pointing the view engine to a specific master page file. Now that we’ve done the basic plumping we need to make the rest of the view engine aware of the new skin name parameter, {2}


The next thing we need to do it create the code that takes advantage of our newly created parameter, the 3 character placeholder that will make all of this possible. Internally, the VirtualPathProviderViewEngine class uses a method called GetPathFromGeneralName which lets you pass in one of these parameterized strings, inject the proper values and check if that file exists or not. So we’re going to need our own overload of that which enables us to send in the name of the skin so the {2} parameter can have a value injected as well.


This brings us to a slight detour. We need a way to get the name of the current skin, from what ever location we decide to store it for our application. To maintain the readability of the code we will re-factor this logic out to its own method, which will return the name of the active skin. This will also give us one place to update if we change the logic which is used to retrieve the name of the skin.


At the beginning of this post I said I’d be using the query string for simplicity in this exercise, so let’s write a helper method that does just that.


private string GetSkinName(ControllerContext controllerContext){    string skinName =        controllerContext.HttpContext.Request.QueryString["skinName"];    return skinName ?? "Default";}

This method takes one argument, the ControllerContext (this class contains all the data a controller needs to do its thing, including a property to gain access to the HttpContext of the current request) which we’ll use to extract the name of the skin. The name of the skin is retrieved from a query string parameter called skinName and I’ve added some fallback logic which will return Default as the skin name if no query string parameter was found. Not that no logic for making sure that the skin actually exists have been included, that will be taken care of by the GetPathFromGeneralName helper method which we’ll be writing next.


private string GetPathFromGeneralName(IEnumerable<string> locations,    string name, string controllerName, string skinName){    foreach (string location in locations)    {        string virtualPath =            string.Format(CultureInfo.InvariantCulture, location, name,            controllerName, skinName);        if (VirtualPathProvider.FileExists(virtualPath))        {            return virtualPath;        }    }    return null;}

This method takes the array of parameterized location string arrays along with the values which will be injected into the strings with the help of string.format. The method is quite basic; it iterates all of the strings and for each string it injects the values and uses the VirtualPathProvider.FileExists helper method to check if the file exists, if it does then the path is returned, if not then it continues with the next string in the array. If no match could be found the method will return null.


It’s very important that the method returns null and not string.Empty for a negative outcome. If we have a look at the source code for the VirtualPathProviderViewEngine class we see that the ASP.NET MVC team have added a comment for us to read.


// NOTE://// This class makes a differentiation between null strings// and empty strings.  The reason for this is that the// ASP.NET cache cannot store null values, because it returns// null for cache lookups that fail. As such, we use null to// mean "the value is not found in the cache" and empty strings// to mean "the cache is storing an empty value".//// Wherever you find comparisons of null but not empty, or// empty but not null, please leave them as is, so that the// system continues to function correctly.

And since we have no reason to doubt the ASP.NET MVC team, we’ll behave and play along :-)

We’ve now come to the stage where we’ve got all out helped code in place, now all we need to do is to override one method and we’re done! The method that will need some attention is the FindView method and it does just what the name says; it tries to figure out where the requested view can be found.


public override ViewEngineResult FindView(ControllerContext controllerContext,    string viewName, string masterName){    string skinName =        this.GetSkinName(controllerContext);    string controllerName =        controllerContext.RouteData.GetRequiredString("controller");    string viewPath =        this.GetPathFromGeneralName(this.ViewLocationFormats, viewName,        controllerName, skinName);    string masterPath =        this.GetPathFromGeneralName(this.MasterLocationFormats, viewName,        controllerName, skinName);    if (string.IsNullOrEmpty(viewPath))    {        IEnumerable<string> searchedLocations =            this.ViewLocationFormats.Union(this.MasterLocationFormats);        return new ViewEngineResult(searchedLocations);    }    return new ViewEngineResult(this.CreateView(controllerContext,            viewPath, masterPath), this);}

It looks a lot more complicated than it really is. The first order of business is to call our GetSkinName method and retrieve the name of the skin. Next up, extract the name of the controller from the route data. Simple, yeah?

We also need to retrieve the path of the view and mater page and we’ll use the GetPathFromGeneralName helper method we created earlier. Now we have all the information we need to is figure out how the ViewEngineResult object, that we’ll be returning, should be created.


There are two possible constructor overloads for the ViewEngineResult class, one which takes an IView object (signaling success) and one which takes IEnumerable<string> object (signaling failure).


So what the method does next is check if a view path was returned and if it wasn’t then it merges (using LINQ) the values of the ViewLocationFormats and MasterLocationFormats arrays into a single array and returns a new ViewEngineResult object, passing the merged array into the constructor.


The reason it wants you to send in the array is so that the framework can provide you with a lot better error messages than we’re used to from ASP.NET. If no match could be made in our FindView method then the following error message will be returned.


The view 'Index' or its master could not be found. The following locations were searched:

~/Views/{1}/{0}.aspx
~/Views/{1}/{0}.ascx
~/Views/Shared/{0}.aspx
~/Views/Shared/{0}.ascx
~/Skins/{2}/Site.master


Notice how the framework tells us exactly where it tired to located our views and mater pages, this makes it a lot easier when you are trying to track down a bug.


Alright, but what if we did find a match? All we have to do then is to return a new ViewEngine object, passing it an IView object which is created by the CreateView method on the WebFormViewEngine class which we’ve inherited from.


And we’re done! At least with our custom view engine, we still need to tell the ASP.NET MVC framework to use our engine instead of the default one. The best place to do this is inside the global.asax class, more specifically in the Application_Start method.


protected void Application_Start(){    System.Web.Mvc.ViewEngines.Engines.Clear();    System.Web.Mvc.ViewEngines.Engines.Add(new WebFormSkinViewEngine());    RegisterRoutes(RouteTable.Routes);}

And that’s it! Compile and run. Hopefully you should see the standard blue template which is shipped with the framework. Next try adding ?skinName=Red at the end of your URL and hit enter. If everything is working correctly then the background should change to a shade of red instead. Voilá! You’ve just injected a separate master page at runtime.


Please not that if you browse a different page, like the "About” section then the query string parameter isn't persisted. The query string was used to illustrate the dynamic changing of skins at runtime, but a better approach would be to update the GetSkinName method to read the value from a persisted location such as the web.config file or a database.


You can download the entire source code for this post at my codeplex page

| Viewed
times
Filed under: