When ASP.NET WebForms was originally produced, it's design did not lend itself to easy dependency management. Despite dependency injection being a widely considered best-practice in other Object-Oriented languages like C++ or Java (the 'D' in S.O.L.I.D. design), it was not a widely held practice within the .NET community until recently.
When ASP.NET MVC started emerging as the new kid on the block, there was a concerted effort to implement a lot of industry best practices to allow for better separation of concerns, with a cleaner, more plug-able, and more test-able interface. For a lot of developers, this was their first exposure to the various methods of Inversion of Control (Service Location and Dependency Injection). The fact that the development of ASP.NET MVC was done in the open with help from the .NET community greatly helped ensure that this was the case.
In the ASP.NET MVC ecosystem, I stumbled across a nice little library called Unity.MVC3 created by DevTrends that put together a nice approach to using the new IDependencyResolver
interface introduced in MVC3 along with the Unity container from Microsoft Patterns and Practices. Since I was already using various components of the Enterprise Library, of which Unity is a part of, this was a natural fit for me.
Since ASP.NET doesn't offer a built-in mechanism for dependency management, a different approach was required. What I found through online searches was that in the ASP.NET Request Pipeline, you could intercept the Page object at the point immediately after it was created but before any Page Life-cycle methods were called. During this window, we could walk the Page control tree and have Unity resolve any properties decorated with the [Dependency]
attribute (property injection).
The UnityHttpModule
Due to the state-less nature of the web and the HTTP protocol, I needed a mechanism that would allow the objects resolved by the Unity Container to be unique for each request to avoid data from one user being confused with the data from another user. Imagine how upset you would be if Amazon ended up charging you for a new 80" Plasma TV when the only item you added to your cart was a DVD
Fortunately, Unity has a mechanism for producing Child containers from a singly configured Parent container. In order for this to happen though, we need to hook into the ASP.NET request pipeline and create a new child container near the beginning of each request. Something an HttpModule
is well suited for.
The module
This module is mostly a verbatim copy of the code that can be found at the MSDN Patterns & Practices library.
Step 1 – Create a new HttpModule
Create a new Http Module, which is nothing more than a class that implements the System.Web.IHttpModule
interface.
public class UnityHttpModule : IHttpModule
{
}
When implementing this interface, there are two methods that need to be defined: Init()
and Dispose()
. Since our implementation of the module will not be holding onto any unmanaged resources directly, we only need to provide an implementation of the Init()
method as follows:
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
/// <param name="context">An <see cref="T:System.Web.HttpApplication"/> 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.BeginRequest += ContextOnBeginRequest;
context.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
context.EndRequest += ContextOnEndRequest;
}
This method is short and focused, registering the module to handle three different events:
BeginRequest
- Creates a new child container from the parent container.PreRequestHandlerExecute
- This event occurs before the ASP.NET runtime starts executing an event handler, like a Page or a Web Service.EndRequest
- Disposes of the child container to ensure resources are properly garbage collected.
Step 2 - Create the child container for the request
The OnBeginRequest
method simply creates a new child container from the parent container so that each web request gets it's own instances of the registered types.
private void ContextOnBeginRequest( object sender, EventArgs e )
{
ChildContainer = ParentContainer.CreateChildContainer();
}
where the ParentContainer
and ChildContainer
properties are defined as follows:
private IUnityContainer _parentContainer;
private IUnityContainer ParentContainer
{
get { return _parentContainer ?? ( _parentContainer = HttpContext.Current.Application.GetContainer() ); }
}
private IUnityContainer _childContainer;
private IUnityContainer ChildContainer
{
get { return _childContainer; }
set
{
_childContainer = value;
HttpContext.Current.SetChildContainer( value );
}
}
More information about child container usage in Unity can be found here: Using Container Hierarchies.
Step 3 - Determine if this is a request that needs to built up
The OnPreRequestHandlerExecute
event handler is fired just prior to the request being handled by the ASP.NET runtime and is defined as follows:
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
/* static content; no need for a container */
if ( HttpContext.Current.Handler == null )
{
return;
}
var handler = HttpContext.Current.Handler;
ChildContainer.BuildUp( handler.GetType(), handler );
// User controls are ready to be built up after the page initialization in complete
var page = handler as Page;
if ( page != null )
{
page.InitComplete += OnPageInitComplete;
}
}
First, we need to check if the current request is for a static resource (in which case the Http Context Handler will be null) or something that the ASP.NET runtime would be involved with handling. If this is a request that will be handled by ASP.NET, then we get an instance of the child container, have it build-up the current HTTP handler, and register an event handler for the page InitComplete
event.
Step 4 - Build up the control tree
The page InitComplete
event is raised immediately after the page object has been instantiated, but prior to the page life-cycle events being raised. At this point, we have a chance to perform property injection on the page and any nested controls. The body of the OnPageInitComplete
event handler is defined as follows:
private void OnPageInitComplete( object sender, EventArgs e )
{
var page = (Page)sender;
foreach ( Control c in GetControlTree( page ) )
{
var typeFullName = c.GetType().FullName ?? string.Empty;
var baseTypeFullName = c.GetType().BaseType != null ? c.GetType().BaseType.FullName : string.Empty;
// filter on namespace prefix to avoid attempts to build up system controls
if ( !typeFullName.StartsWith( "System" ) || !baseTypeFullName.StartsWith( "System" ) )
{
ChildContainer.BuildUp( c.GetType(), c );
}
}
}
Here we walk the control tree for the page and have the child container build up each control. In order to prevent a lot of potential reflection overhead for controls that have not been authored by you, we limit the build up action to only controls that are not defined in the System.*
namespaces. This could be further optimized in your own project by only looking for namespaces that you control, but in order to make this into a universal NuGet package that others could use, I simply took the route of filtering out the System
namespace.
The GetControlTree()
method (shown below) is just a wrapper around recursively walking the control tree.
private static IEnumerable GetControlTree( Control root )
{
if ( root.HasControls() )
{
foreach ( Control child in root.Controls )
{
yield return child;
if ( child.HasControls() )
{
foreach ( Control c in GetControlTree( child ) )
{
yield return c;
}
}
}
}
}
Step 5 - Clean-up
The ContextOnEndReqesut
method gets called at the tail-end of the request pipeline and allows us to properly dispose of the child container, which in turn disposes of all objects registered in the container.
private void ContextOnEndRequest( object sender, EventArgs e )
{
if ( ChildContainer != null )
{
ChildContainer.Dispose();
}
}
Recap
In this post, we saw how to create an HTTP Handler that will intercept the constructed Page/Control just before any life-cycle events are fired allowing us to inject any dependencies from our container (Unity).
In the next post, we will see how to register this new Handler into the request pipeline without having to edit the web.config
file.