Sunday, November 01, 2009 #

Dynamically Switching between Master Pages in ASP.NET MVC

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.

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.

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).

    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);
        }
    }

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.

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.

Passing the master page name into the View method

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.

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.

        public ActionResult SomePage()
        {
            return View("SomePage", "MasterTwo");
        }
        public ActionResult SomeOtherPage()
        {
            var view = View();
            view.MasterName = "MasterTwo";
            return view;
        }

 

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.

This leads us nicely on to the next possible solution, of having this work done for us globally by the controller.

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.
Overriding OnActionExecuted on the Controller class

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.

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.

You could even detect whether the MasterName property had been set, and not override it if an action has already explicitly set it.

        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var action = filterContext.Result as ViewResult;
            if (action != null && String.IsNullOrEmpty(action.MasterName))
            {
                action.MasterName = "MasterThree";
            }
            base.OnActionExecuted(filterContext);
        }

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.

 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.

Custom View Engine

Moving further up the processing chain, the Custom ViewEngine allows the application to specify the master file for any request.

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.

    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);
        }
    }

This is registered in place of the built in view engine like so:

            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new ThemedViewEngine());

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.

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.

ViewPage - OnPreInit

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.

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.

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.

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!

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.

Knowing that ViewPage is inherited from the ASP.NET Forms Page, so we can create ThemableViewPage

    public class ThemedViewPage<T> : ViewPage<T> 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<Object> { }

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!)

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.

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 .

Summary

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.

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.


Technorati tags: , , ,

posted @ Sunday, November 01, 2009 6:44 PM | Feedback (1)

Wednesday, October 28, 2009 #

Legacy .NET 1.1 COM in a classic ASP application after installing the .NET 2.0 Runtime

I've just had a fun evening trying to come up with a half-suitable solution for this problem and thought it worth documenting here although my next entry isn't due for a couple of days.

As previously mentioned, we run a legacy classic ASP system for most of our clients and we're in the process of writing a replacement system in .NET.

This week we finished the user acceptance testing  on the first deployable .NET application - a small interface built as a replacement for a small portion of that classic ASP system and needed therefore to build it to a live server.

The servers are fairly locked down as you'd expect and a change request had to be submitted to get .NET Framework 3.5 installed along with the .NET 2.0 runtime. I installed some routine security updates at the same time and rebooted the servers one at a time whilst testing that the legacy sites still worked.

All seemed great until the next day when a very tired CTO turned up asking if he could get some sleep tonight because he had been receiving Nagios alerts all night telling him that the live sites were failing over.
Looking at the logs we could see that the application pools for most of the legacy sites had been recycling every hour and freezing for a vast majority of that hour - and with a bit of creative debugging the problem was isolated to the CreateObject calls to a pile of both in-house and third party managed COM objects installed on the server.

Rather than failing gracefully and logging an error, for some reason the calls were simply backing the server up and causing it to stop responding to requests.

I had a hunch that this was to do with the new version of the runtime we had installed and set about trying to prove that this was the case.

When an un-managed process (in this case our IIS worker process) is started up, it is obviously done so without initializing the .NET framework. If you then attempt to load a managed assembly into that process (in this case our COM object), the most recent version of the framework will be loaded into that process.

At the moment, only one version of the .NET Framework is capable of being loaded into a single process, and this is determined on a first come first served basis.  99% of the time if you load a .NET 1.1 assembly into a .NET 2.0 process,  things will work fine because there are few breaking changes between the two runtimes - however there are cases where this does not hold true and as people always say "your mileage may vary".

In IIS, the application was set up as being a .NET 1.1 website - but I wasn't convinced that this had much to do with anything and decided to try out a little hack based on my previous experience with desktop application development, COM and the different .NET runtimes.

When you deploy a .NET application, you can supply a configuration file with various overrides/version-mappings/etc alongside that application with the convention <applicationname>.exe.config. This means that you can also supply a configuration file for an application you don't "own" and modify that way it works on your machine.

<?xml version="1.0"?>
<configuration>
 <startup>
 <supportedRuntime version="v1.1.4322"/>
 <requiredRuntime version="v1.1.4322"/>
 </startup>
</configuration>

Some of the suggestions on the internet were to stick this in a web.config file at application root, but:

  1. I've read elsewhere that this is not supported in web applications 
  2. I had a feeling that this directive would be ignored because the web.config file would only be read when a .NET resource was requested.

I decided that my nail was much bigger, and instead created an inetsrv.exe.config, a w3wp.exe.config and a dllhost.exe.config with the above directive in and placed them in system32/inetsrv and system32 respectively.

Restarting IIS and hitting my test page with all the CreateObjects (my fail case) yielded in a success and my next step was therefore going to be to find something that didn't involve forcing all IIS processes and god knows what else to run with .NET 1.1 and .NET 1.1 only.

As previously mentioned, which runtime loaded into the worker process depends entirely on who first requests that a .NET assembly be loaded into process. The alternative in the desktop world and certain other places would be to manually invoke the .NET runtime with an explicit call to CorBindToCurrentRuntime  or similar.

I decided the best approach would be to force the IIS worker process to do this for me by invoking some .NET code in the context of the application. This would then use the framework version specified in the application and solve all the problems.


A few options were considered such as:

  1. Creating a global.asax to get executed when application starts
  2. Creating a web.config file as outlined in a few of the responses to questions like this on the internet
  3. Creating a dummy aspx file which could be requested by global.asa on application startup

1 and 2 wouldn't work because no requests are ever made to the server through the aspnet extension, so 3 seemed to be the only reasonable solution.

The following aspx file was created on the server:

<%@ Page Language="vb" AutoEventWireup="false" %>

<p>Hello World</p>

And the following code added to global.asa:

if not Application("hasInitializedNet") then

	Dim reqUrl 
	reqUrl = "http://" & Request.ServerVariables("LOCAL_ADDR") & "/default.aspx"

	Dim objXmlHttp
	Dim strHTML

	Set objXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP")

	objXmlHttp.open "GET", reqUrl , False

	objXmlHttp.send

	strHTML = objXmlHttp.responseText
	
	Set objXmlHttp = nothing

	Application("hasInitializedNet") = true

end if


The application was restarted and voila, the page was requested when the first user hit the server and the right version of.NET was loaded into the application from the start.

I can't think of a neater way that would allow us to still have our .NET 3.5 applications on the same machine and this solution is good enough for now (Until we get another pair of servers in for the new systems).

Feel free to leave any better suggestions in the comments!


Technorati tags: , , ,

posted @ Wednesday, October 28, 2009 11:32 PM | Feedback (0)

Wednesday, October 21, 2009 #

Why Linq2NHibernate isn't ready for production use

I was quite excited when Ayende announced that he had written a Linq provider for NHibernate which was of production quality.

It came with a few caveats, it could only do what criteria currently supported and they hadn't implemented any sort of join support. I considered these to be perfectly reasonable items on the todo list, given that they are actively working on creating a more complete linq provider that does everything people would want it to do.

I do the vast bulk of my queries using the criteria API, written as specifications against thin repositories, and adding a LinqSpecification was a simple task. I decided that given the limitations described above to follow a few simple rules for when I could use a Linq query such as "only use Linq when querying the properties of a single type of object".

Having Linq in my specifications gave some real benefits, such as strong typing and compile time support for checking the properties being queried against. Normally if you change the name of a property on any domain object and forget to update the text in a criteria or hql query you'd only catch this when you ran your unit tests. (Or worse, during product testing/use!)

I was using it quite happily for a week or so until I stopped to review the sql it was generating under the hood for even the simplest of queries.

Consider the following objects:

    public class Blog
    {
        public virtual int Id
        {
            get;
            protected set;
        }

        public virtual String Name
        {
            get;
            set;
        }
    }

    public class BlogEntry
    {
        public virtual int Id
        {
            get;
            set;
        }

        public virtual Blog Blog
        {
            get;
            set;
        }

        public virtual String Content
        {
            get;
            set;
        }
    }

With the following mappings: 

    public class BlogMap : ClassMap<Blog>
    {
        public BlogMap()
        {
            Id(x => x.Id);
            Map(x => x.Name).Not.Nullable();
        }
    }

    public class BlogEntryMap : ClassMap<BlogEntry>
    {
        public BlogEntryMap()
        {
            Id(x => x.Id);
            Map(x => x.Content).Not.Nullable();
            References(x => x.Blog).Not.Nullable();
        }
    }

Fairly straight forward stuff. Now imagine I have a blog object in hand and I wish to query for all the entries that belong to that blog (We could easily imagine asking this being a query where we ask for the entries from that blog between a certain date range).

The query looks something like this: 

            var linqResults = (
                        from entry in session.Linq<BlogEntry>()
                        where entry.Blog  == blog
                        select entry
                      ).ToList();

Again, very straight forward - and you'd expect the query to be so as well - so have a look!
SELECT this_.Id as Id1_1_, this_.Content as Content1_1_, this_.Blog_id as Blog3_1_1_, blog1_.Id as Id0_0_, blog1_.Name as Name0_0_ FROM "BlogEntry" this_ left outer join "Blog" blog1_ on t his_.Blog_id=blog1_.Id WHERE this_.Blog_id = @p0;@p0 = 1
What WHAAT?!

If you can't see the problem here then go and ask your DBA if you have one, because I can guarantee they'll not be happy if you start littering your specifications with this kind of query.

  1. Why are we doing a join to the Blog table? We don't need to do a join to the Blog table, we should be comparing the id situated in the BlogEntry table.
  2. Why are we bringing back al the information from the blog table as well as the blog entry? Imagine you're bringing back 50 such blog entries - you'd be bringing back 50 identical blog objects too.
  3. It's a left outer join too, your DBA is going to hit the roof! (Mine definitely would - you should only be using these where it makes sense to be using them.
Check out the equivalent Criteria based query:
           
            var criteria = session.CreateCriteria<BlogEntry>()
                                .Add(Restrictions.Eq("Blog", blog)).List<BlogEntry>();


Generating the following SQL:

SELECT this_.Id as Id1_0_, this_.Content as Content1_0_, this_.Blog_id as Blog3_1_0_ FROM "BlogEntry" this_ WHERE this_.Blog_id = @p0;@p0 = 1


Much more sensible.

You might be of the persuasion that you don't really care, and that's acceptable in some cases I guess- but I'm of the opinion that if all of your queries look like the above then you've got some real issues. Our product has to service the needs of some fairly large customers with some fairly heavy load and the system chugging at all is going to get us in a lot of trouble.

Take for example the following (perfectly reasonable) query if our objects were a little more complex.

            var complexResult = (
                    from entry in session.Linq<BlogEntry>()
                    where entry.Category == category &&  entry.Blog.User == user
                    select entry
                ).ToList();

This will generate the following SQL:

SELECT this_.Id as Id1_3_, this_.Content as Content1_3_, this_.Blog_id as Blog3_1_3_, this_.Category_id as Category4_1_3_, blog2_.Id as Id0_0_, blog2_.Name as Name0_0_, blog2_.User_id as User3_0_0_, user3_.Id as Id3_1_, user3_.Name as Name3_1_, category1_.Id as Id2_2_, category1_.Name as Name2_2_ FROM "BlogEntry" this_ left outer join "Blog" blog2_ on this_.Blog_id=blog2_.Id left outer join "User" user3_ on blog2_.User_id=user3_.Id left outer join "Category" category1_ on this_.Category_id=category1_.Id WHERE (this_.Category_id = @p0 and blog2_.User_id = @p1);@p0 = 1, @p1 = 1

Every single property of every single referenced object being brought back even though I've only asked for BlogEntry.

Without trying to sound like a complete arse, this is not the definition of production quality I am familar with!

 I really do look forward to the proper release of Linq2NHibernate, but until then I'm sticking to Hql And Criteria because I can trust them to keep my DBA happy.


Technorati tags: , , ,

posted @ Thursday, October 22, 2009 12:00 AM | Feedback (4)

Times have changed

I had a blog here once, it was mostly personal with a bit of code talk thrown in and the focus of the website was actually in showcasing all the projects I'd completed so people could see that I wasn't just a nerd who spent all his time on IRC and played games - I was a productive nerd who spent all his time on IRC and played games.

I'd been spending the vast majority of my time since leaving university meandering around and writing code for various people whilst learning on my own personal projects.

Roughly a year ago I went and got myself a "real job" in the "real world".

The job I managed to wrangle myself into was that of a standard .NET Web developer, messing about in ASP.NET 2.0 on a hideous system that was a mess of badly written code written by people who were very new to .NET having worked on ASP projects previously.

This project was ditched and because it was just a glorified CMS, it was moved across to an open source platform (Drupal) and because I've got no interest in maintaining open source CMS systems switched teams to help them write a bit of .NET for their (again) legacy ASP system.

I now find myself a year later the technical lead/architect/whatever on a grand old web application written in .NET 3.5 using all the fancy modern technology that modern .NET web developers are supposed to be using. (ASP.NET MVC, jQuery, NHibernate/Fluent/Etc, xUnit etc)

This grand old web application that we work on is for a host of really large companies (household names in a lot of cases), and we already have an old version of the application written in ASP which we're looking to move away from. There are over nine years of knowledge and experience captured in that old application and thus re-imagining it in .NET is a challenging prospect.

Some of the requirements that have come up over time need us to come up with some rather innovative solutions when bringing them across to this brave new world and thus I've ended up with a whole suite of technical problems which we've had to overcome.

This brings me to the point of this blog, I aim to document at least some of these problems here over time in order to foster either some discussion (Probably a bit hopeful), or at least to leave a vague trail in Google for other developers to follow when they encounter similar.

At no point in this blog am I going to mention who I work for, or what the product is I work on, I am an opinionated person and some opinions are best kept as far away from the workplace as possible.


Technorati tags:

posted @ Wednesday, October 21, 2009 11:37 AM | Feedback (0)

Copyright © Rob Ashton

Design by Rob Ashton, Based On A Design By Bartosz Brzezinski