Chris Tavares and I were chatting yesterday morning about an idea Chris had: building a simple, reusable Http Module that gives folks DI scoped to the Application, Session, and Request.  Yesterday afternoon, during the p&p Dev team's weekly "Code Kata" we threw together a spike/proof of concept in a couple of hours.  "Code Kata" is a three hour block of time that the p&p Dev team uses for a number of things:

  • cross-project pollination of ideas, things that work, things that don't work across project teams
  • investigating new and emerging technologies that p&p may work with in the future
  • investigating new and emerging platforms to determine if p&p may want to provide guidance in the future
  • play with new shiny bits :-)

There are no unit tests, just simple acceptance tests written as a really simple web site.  If the site works and shows the right text, the test passes.  We started with a list of requirements (scaled to just the application level) on the whiteboard that looked sort of like this (which I am creating from memory):

  • Create a DI container for the application
  • Create a way to get to the container ( we choose an extension method on the Application class)
  • Allow a way to configure the container
  • Allow DI to work for pages
  • Allow DI to work for user controls
  • Allow DI to work for master pages
  • Allow DI to work for ASMX web services
  • Allow the above functionality in a simple and self contained way

So, we created a simple Web Site Application and a DLL to hold the custom HttpModule.  We created a simple web page that needed a property injected into it.  We then wrote the code to make my "test" page work properly.  First we wrote an extension method for HttpApplication.  After a short bit it changed to one for HttpApplicationState:

using System.Web;
using Microsoft.Practices.Unity;

namespace UnityForTheWebLib
{
    public static class HttpApplicationStateExtensions
    {
        private const string GlobalContainerKey = "Your global Unity container";

        public static IUnityContainer GetContainer(this HttpApplicationState application)
        {
            application.Lock();
            try
            {
                IUnityContainer container = application[GlobalContainerKey] as IUnityContainer;
                if(container == null)
                {
                    container = new UnityContainer();
                    application[GlobalContainerKey] = container;
                }
                return container;
            }
            finally
            {
                application.UnLock();
            }
        }
    }
}

Basically, we have lazy initialization of the container and a mechanism to stuff it into the application state.  And then the initial HttpModule that only does injection on a Page:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using Microsoft.Practices.Unity;

namespace UnityForTheWebLib
{
    public class UnityHttpModule : IHttpModule
    {
        #region IHttpModule Members

        ///<summary>
        ///Initializes a module and prepares it to handle requests.
        ///</summary>
        ///
        ///<param name="context">An <see cref="T:System.Web.HttpApplication"></see> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application </param>
        public void Init(HttpApplication context)
        {
            context.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
        }

        ///<summary>
        ///Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"></see>.
        ///</summary>
        ///
        public void Dispose()
        {
        }

        #endregion

        private void OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
            IHttpHandler handler = HttpContext.Current.Handler;
            HttpContext.Current.Application.GetContainer().BuildUp(handler.GetType(), handler);
        }
    }
}

This is a very simple module that calls BuildUp on the page. 

At this point we added another test, and implemented it.  After a few iterations of this we ended up with tests for injection into a User Control and a Master Page, an interface that needed to be configured on the container.  The final implementation of the Http Module looks like this

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using Microsoft.Practices.Unity;

namespace UnityForTheWebLib
{
    public class UnityHttpModule :
IHttpModule
   
{
        #region IHttpModule Members

       
///<summary>
        ///
Initializes a module and prepares it to handle requests.
       
///</summary>
        ///
        ///<param name="context">
An <see cref="T:System.Web.HttpApplication"></see> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application
</param>
       
public void Init(HttpApplication context)
        {
            context.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
        }

       
///<summary>
        ///
Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"></see>
.
       
///</summary>
        ///
       
public void Dispose()
        {
        }

       
#endregion

        private void
OnPreRequestHandlerExecute(object sender, EventArgs e)
        {
            IHttpHandler handler = HttpContext.Current.Handler;
            HttpContext.Current.Application.GetContainer().BuildUp(handler.GetType(), handler);

           
// User Controls are ready to be built up after the page initialization is complete
           
Page page = HttpContext.Current.Handler as Page;
            if (page != null)
            {
                page.InitComplete += OnPageInitComplete;
            }
        }

       
// Get the controls in the page's control tree excluding the page itself
       
private IEnumerable<Control> GetControlTree(Control root)
        {
            foreach (Control child in root.Controls)
            {
                yield return child;
                foreach (Control c in GetControlTree(child))
                {
                    yield return c;
                }
            }
        }

       
// Build up each control in the page's control tree
       
private void OnPageInitComplete(object sender, EventArgs e)
        {
            Page page = (Page) sender;
            IUnityContainer container = HttpContext.Current.Application.GetContainer();
            foreach (Control c in GetControlTree(page))
            {
                container.BuildUp(c.GetType(), c);
            }
        }
    }
}

Configuration of the container can happen in the Global Application_Start handler like this:

using System;
using Microsoft.Practices.Unity;

namespace UnityForTheWebTestSite
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            IUnityContainer c = Application.GetContainer();
            c.RegisterType<IControlData, ControlData1>();
        }
    }
}

So, from the initial requirements we have this:

  • Create a DI container for the application Lazy initialization
  • Create a way to get to the container ( we choose an extension method on the Application class) Extension Method
  • Allow a way to configure the container Application Start
  • Allow DI to work for pages  Add the HttpModule and Attributes
  • Allow DI to work for user controls Add the HttpModule and Attributes
  • Allow DI to work for master pages Add the HttpModule and Attributes
  • Allow DI to work for ASMX web services
  • Allow the above functionality in a simple and self contained way This is all in a single module
We met the last requirement (simple and self contained) since all we did to the web application was add a single line to the web config.  That met the requirement.

The last area is one we met, but we are not happy with the solution.  Basically, we need to do a bit more research and see if there is a way to get into the pipeline for an ASMX request early enough to do injection and not screw things up for the normal use case or the Ajax extensions.  Until we get this figured out right, the solution is to have your Web Service constructor ask the container to do injection.  This is an ugly hack and looks something like this:

using System.ComponentModel;
using System.Web;
using System.Web.Services;
using Microsoft.Practices.Unity;

namespace UnityForTheWebTestSite
{
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    // [System.Web.Script.Services.ScriptService]
    public class MyWebService : System.Web.Services.WebService
    {
        public MyWebService()
        {
            HttpContext.Current.Application.GetContainer().BuildUp(this);
        }

        [Dependency]
        public IControlData Data
        {
            get;
            set;
        }

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World " + Data.GetText();
        }
    }
}

To add the ability to have a container at the Session level and the request level should be as simple as following the pattern for the extension method and the http handler above. (I will leave it as an exercise for the reader.) You could use the same container for everything, or have the Session container a child of the application container, and the request container a child of the session container.  When and if we do a real implementation (as opposed to a proof of concept) we will figure out the best approach for most cases.  When and if this happens, I will update my CWAB with Unity solution and dramatically simplify it. :-)