<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>ASP.NET Forms</title>
        <link>http://www.codeofrob.com/category/9.aspx</link>
        <description>ASP.NET Forms</description>
        <language>en-GB</language>
        <copyright>Rob Ashton</copyright>
        <generator>Subtext Version 2.1.2.2</generator>
        <item>
            <title>Dynamically Switching between Master Pages in ASP.NET MVC</title>
            <link>http://codeofrob.com/archive/2009/11/01/dynamically-switching-between-master-pages-in-asp.net-mvc.aspx</link>
            <description>&lt;p&gt;&lt;font face="Arial"&gt;When developing a web application that's designed for re-deployment in a number of different environments (such as a blogging engine/forum system/etc), it's helpful to be able to re-skin and re-structure  the application without modifying any application files.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;To a very large extent, this can be achieved through the use of an alternative set of cascading style sheets and this works for a large number of people. However if you take a look on programming websites such as Stack Overflow the question of how to change the master page at runtime is still an oft-asked one.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;In ASP.NET Forms the solution was to simply subclass Page, override PreInit and change the MasterPage property based on some application variable. The master page specified by the corresponding ASPX file could even be read out and used to determine which themed master page to use. (A useful function if you had multiple master pages used throughout the site).&lt;/font&gt;&lt;/p&gt;
&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;    public class ThemedPage : Page
    {
        protected override void OnPreInit(EventArgs e)
        {
            if (this.MasterPageFile.EndsWith("MasterOne.Master", StringComparison.InvariantCultureIgnoreCase))
            {
                // TODO: Some logic here to find the right master page based on theme!
                this.MasterPageFile = "/Views/Shared/MasterThree.Master";
            }

            base.OnPreInit(e);
        }
    }&lt;/pre&gt;
&lt;p&gt;&lt;font face="Arial"&gt;In ASP.NET MVC the playing field has been altered somewhat, and there are a number of options to consider when creating an application with dynamic master pages.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;The most championed solutions found on the afore-mentioned programming websites are to either pass the master page name into the View() method when returning a ViewResult , or to create a custom view engine which specifies the master page.&lt;/font&gt;&lt;/p&gt;
&lt;h5&gt;Passing the master page name into the View method&lt;/h5&gt;
&lt;font face="Arial"&gt;
&lt;p&gt;&lt;font face="Arial"&gt;When returning a ViewResult via any of the built in methods (Controller.View()) the option is provided to pass in the name of as master page - and the default view engine will look for a master page with that name in the ~/Views/Shared directory.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Alternatively you can modify the ViewResult  before returning it from your action method - which is probably the preferred option in most cases  as you probably don't want to be passing in the name of the view all the time too.&lt;/font&gt;&lt;/p&gt;
&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;        public ActionResult SomePage()
        {
            return View("SomePage", "MasterTwo");
        }&lt;/pre&gt;
&lt;/font&gt;&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;        public ActionResult SomeOtherPage()
        {
            var view = View();
            view.MasterName = "MasterTwo";
            return view;
        }&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;It is obvious however from these two examples that this is  an un-maintainable solution; having to specify the master page on every single action is going to get tedious and if you decide to change this solution for a different one later on you're going to have to go back and modify all of those method calls.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;This leads us nicely on to the next possible solution, of having this work done for us globally by the controller.&lt;/font&gt;&lt;/p&gt;
&lt;/font&gt;&lt;font face="Arial"&gt;It would be possible to pass in the name of a different master page by using a helper somewhere that knew the details of the current theme and therefore the names of the master pages it uses.&lt;/font&gt;&lt;/font&gt;
&lt;h5&gt;Overriding OnActionExecuted on the Controller class&lt;/h5&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;font face="Arial"&gt;Rather than specify the master page name as the result of every single Action method, you could either create a base controller or override OnActionExecuted on a case-by-case basis.&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;OnActionExecuted gives you a chance to modify the result after an action has been invoked, which means you can take the ViewResult which was returned by an action and set the MasterName on it in this location.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;You could even detect whether the MasterName property had been set, and not override it if an action has already explicitly set it.&lt;/font&gt;&lt;/p&gt;
&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var action = filterContext.Result as ViewResult;
            if (action != null &amp;amp;&amp;amp; String.IsNullOrEmpty(action.MasterName))
            {
                action.MasterName = "MasterThree";
            }
            base.OnActionExecuted(filterContext);
        }&lt;/pre&gt;
&lt;/font&gt;
&lt;p&gt;&lt;font face="Arial"&gt;This gives you the power of being able to specify a master page per controller and still have the flexibility of overriding it per action. &lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt; It's still not ideal though, there is a certain amount of manual work required in doing this that you wouldn't want if you were going to be developing a large system with a substantial number of controllers or actions.&lt;br /&gt;
&lt;/font&gt;&lt;/p&gt;
&lt;h5&gt;Custom View Engine&lt;/h5&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Moving further up the processing chain, the Custom ViewEngine allows the application to specify the master file for any request.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;For the purposes of this example I'll derive my custom view engine from the standard built-in WebFormViewEngine as it requires the least work to get up and running.&lt;/font&gt;&lt;/p&gt;
&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;    public class ThemedViewEngine : WebFormViewEngine
    {
        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (string.IsNullOrEmpty(masterName))
            {
                masterName = "MasterOne";
            }
            return base.FindView(controllerContext, viewName, masterName, useCache);
        }
    }&lt;/pre&gt;
&lt;/font&gt;
&lt;p&gt;&lt;font face="Arial"&gt;This is registered in place of the built in view engine like so:&lt;/font&gt;&lt;/p&gt;
&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new ThemedViewEngine());&lt;/pre&gt;
&lt;/font&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Now let's take a look at that code - passed in to the method we're overriding (FindView) is a string called masterName.  This is where that string ends up if you use either of the two previous two methods to specify the master page.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;It follows on therefore that just like the last example you can do a check here to see if a master page has already been specified by the previous two methods, and specify one if one has not been set already.&lt;/font&gt;&lt;/p&gt;
&lt;h5&gt;ViewPage - OnPreInit&lt;/h5&gt;
&lt;p&gt;&lt;font face="Arial"&gt;All of the above methods completely ignore the master page directive set in the view itself - which is in my opinion a little bit bonkers.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;By specifying a master page in the ASPX view, you allow the compiler to verify that the right ContentPlaceHolders are overridden  and therefore if you enable compilation of your views you get a check that your view are valid.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Consider for  example the site that has a number of base master pages, one of my personal sites for example has three master pages which are used in different circumstances and each of them have different ContentPlaceHolders because they're for use in completely different functional situations.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;The application is probably unaware of these directives (and indeed should be probably be de-coupled from such concerns as whether a page is using a particular master page or not) and therefore shouldn't be making the decision as to which master page to use!&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;ASP.NET MVC is built on top of ASP.NET Forms however, so it turns out that we can ignore the delightfully helpful methods given to us in ASP.NET MVC and skip right back to our original solution of overriding OnPreInit on the base Page class.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Knowing that ViewPage is inherited from the ASP.NET Forms Page, so we can create ThemableViewPage&lt;/font&gt;&lt;/p&gt;
&lt;font face="Arial"&gt;
&lt;pre class="brush: csharp;" title="code"&gt;    public class ThemedViewPage&amp;lt;T&amp;gt; : ViewPage&amp;lt;T&amp;gt; where T : class
    {
        protected override void OnPreInit(EventArgs e)
        {
            if (this.MasterPageFile.EndsWith("MasterOne.Master", StringComparison.InvariantCultureIgnoreCase))
            {
                // TODO: Some logic here to find the right master page based on theme!
                this.MasterPageFile = "/Views/Shared/MasterThree.Master";
            }

            base.OnPreInit(e);
        }
    }

    public class ThemedViewPage : ThemedViewPage&amp;lt;Object&amp;gt; { }&lt;/pre&gt;
&lt;/font&gt;
&lt;p&gt;&lt;font face="Arial"&gt;Note: I create a generic version and a non generic version so we can use it on non strong-typed pages (Some people use these, I don't know why!)&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;We can use the same theme code we used in the original example to solve the problem - and best of all, it is still compatible with the previous three methods - so if a different master page is specified by either an Action, a Controller or the ViewEngine this logic will still work.&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;The only caveats that I can see are that this method is quite dependent on the default WebFormView implementation, and that every view needs to be set up to inherit from this custom ViewPage .&lt;/font&gt;&lt;/p&gt;
&lt;h5&gt;Summary&lt;/h5&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;font face="Arial"&gt;Switching between master pages is still a bit of a fuzzy topic, and the options given to us in ASP.NET MVC are a bit inadequate. There is still the question as to whether we should be attempting to do this at all given how powerful CSS is - but if you really need to, this blog entry should give you a helpful pointer in the right direction.&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;In the projects I own technically where this sort of functionality is going to be requested, I'll be sticking to the OnPreInit method until something better comes up.&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;font face="Arial"&gt;&lt;hr /&gt;
Technorati tags: &lt;a rel="tag" href="http://technorati.com/tags/ASP.NET"&gt;ASP.NET&lt;/a&gt;, &lt;a rel="tag" href="http://technorati.com/tags/ASP.NET+MVC"&gt;ASP.NET MVC&lt;/a&gt;, &lt;a rel="tag" href="http://technorati.com/tags/Master+Pages"&gt;Master Pages&lt;/a&gt;, &lt;a rel="tag" href="http://technorati.com/tags/Themes"&gt;Themes&lt;/a&gt;&lt;/font&gt;&lt;/font&gt;&lt;/p&gt;
&lt;/font&gt;&lt;img src="http://codeofrob.com/aggbug/4.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Rob Ashton</dc:creator>
            <guid>http://codeofrob.com/archive/2009/11/01/dynamically-switching-between-master-pages-in-asp.net-mvc.aspx</guid>
            <pubDate>Sun, 01 Nov 2009 18:44:54 GMT</pubDate>
            <wfw:comment>http://codeofrob.com/comments/4.aspx</wfw:comment>
            <comments>http://codeofrob.com/archive/2009/11/01/dynamically-switching-between-master-pages-in-asp.net-mvc.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://codeofrob.com/comments/commentRss/4.aspx</wfw:commentRss>
            <trackback:ping>http://codeofrob.com/services/trackbacks/4.aspx</trackback:ping>
        </item>
    </channel>
</rss>