Dependency Injection and UserControls with Castle MicroKernel

The Castle MicroKernel is a lovely software construct that allows you to create some extremely powerful and configurable systems while abastracting most of the complexity.  I could spend several blog entries describing what the Kernel is and why it is cool, but smarter folks than I have already done that.  What I wanted to talk about in this post is channeling the Kernel's power into an ASP.Net web application.

One of the key aspects to the Kernel is Dependency Injection.  The Kernel will examine the constructors for the objects it manages and make sure all of the required external objects are provided when you create an instance of that object.  For Example:

public DashboardPresenter(IDashboardView view) { ... }

When you request a DashboardPresenter object from the Kernel, it will try and instanitate an IDashboardView object and pass that to the DashboardPresenter constructor for you.  This saves you from having to know about and create all of the objects your Presenter relies on.  That was the 30 second DI tutorial :)

The problem I am trying to address is achieving this injection functionality in the case of Web UserControls.  The problem with UserControls is they are usually compiled the very first time someone accesses the Web Applicaiton that uses the control.  The ASP.Net runtime parses and compiles the control on the fly to a temporary assembly.  Anytime you change something on the control, it is re-compiled.  ASP.Net 2.0 now offers the ability to pre-compile Pages and UserControls before you deploy them to your production server.  This removes the need for the runtime to compile it on the fly.

The problem with pre-compiling UserControls is three fold:

  1. The ASP.Net compiler will generate one assembly per folder that contains UserControls in your project.  The assembly names are randomly genrated in "App_Web@@@@@@" form.  This can potentially result in a large number of generated assemblies.
  2. Pre-compilation generates a new class to represent your UserControl.  For example, you have a UserControl called BatchTracker.Views.DashboardView, the ASP.net compiler will generate a new class that inherts from DashboardView called DashboardView_ascx.  The subclass contains all of the code generated as a result of parsing the .ascx portion of your UserControl.
  3. Pre-compliation is very slow.  Anyone using the ASP.Net 2.0 Web Site Project can testify to the snail's paces of building.

K. Scott Allen has an excellent article to address problem number 1 entitled Using MSBuild and ILMerge to Package User Controls For Reuse.  Scott explains how to pre-compile your controls and package them all into a single assembly. 

With our UserControls nicely packaged, we are almost ready to use them with our DI Kernel. When we add components (Classes) to the Kernel we specify them with a fully qualified name ala "BatchTracker.Views.DashboardView, BatchTracker.Views".  As I mentioned in point 2 above, the class we really want to add to the Kernel is the DashboardView_ascx class.  Because I'm lazy don't want to have to add my compoents with a _ascx tacked on the end, I wrote a custom inspector for the MicroKernel that will determine if a component is a UserControl and if so, it will add the pre-compiled subclass inplace of the specified class.  Below I've listed the code for the WebUserControlModelInspector.

using System;
using System.Web;
using System.Web.UI;
 
using Castle.Model;
using Castle.MicroKernel;
using Castle.MicroKernel.ComponentActivator;
 
namespace WCPierce.MicroKernel.ComponentActivator
{
  [Serializable]
  public class WebUserControlComonentActivator : DefaultComponentActivator
  {
    public WebUserControlComonentActivator(ComponentModel model, IKernel kernel, ComponentInstanceDelegate onCreation, ComponentInstanceDelegate onDestruction) 
      : base(model, kernel, onCreation, onDestruction)
    {
    }
 
    protected override object CreateInstance(object[] arguments, Type[] signature)
    {
      object instance;
 
      Type implType = Model.Implementation;
 
      if (Model.Interceptors.HasInterceptors)
      {
        try
        {
          instance = Kernel.ProxyFactory.Create(Kernel, Model, arguments);
        }
        catch (Exception ex)
        {
          throw new ComponentActivatorException("WebUserControlComponentActivator: could not proxy " + Model.Implementation.FullName, ex);
        }
      }
      else
      {
        try
        {
          HttpContext currentContext = HttpContext.Current;
          if (currentContext == null)
          {
            throw new InvalidOperationException("System.Web.HttpContext.Current is null.  WebUserControlComponentActivator can only be used in an ASP.Net environment.");
          }
 
          Page currentPage = currentContext.Handler as Page;
          if (currentPage == null)
          {
            throw new InvalidOperationException("System.Web.HttpContext.Current.Handler is not of type System.Web.UI.Page");
          }
 
          instance = currentPage.LoadControl(implType, arguments);
        }
        catch (Exception ex)
        {
          throw new ComponentActivatorException("WebUserControlComponentActivator: could not instantiate " + Model.Implementation.FullName, ex);
        }
      }
 
      return instance;
    }
  }
}

The code is fairly self explanatory, if the compenent derives from System.Web.UI.UserControl we search its assembly for a subclass (the precompiled version).  The check for the __initialized field is just incase someone actually adds the precompiled version directly to the Kernel.  I couldn't figure out a better way to determine if the component was already precompiled.  Any input is appreciated.

To use the Inspector simply call

Kernel.ComponentModelBuilder.AddContributor(new WebUserControlModelInspector())

before you add any components to the Kernel.

My next post will address the issue of Activating (instantiating) a UserControl derived component in the context of the Kernel.

posted @ Friday, August 18, 2006 4:52 PM


Print

Comments on this entry:

# re: Dependency Injection and UserControls with Castle MicroKernel

Left by Everett at 10/3/2006 12:57 PM

I'm really enjoying looking through the work you've done on this. I've been giving a lot of thought to how to best implement MVP in an upcoming web project for our organization. <br /><br />I did have one question about this post though. You say that the code listing is for WebUserControlModelInspector but the class in the listing is named WebUserControlComonentActivator. Am I being thick or is the listing possibly incorrect.<br />

# re: Dependency Injection and UserControls with Castle MicroKernel

Left by Bill Pierce at 10/4/2006 3:01 PM
Gravatar

Everett:<br />The WebUserControlModelInspector is required for us to tell the Kernel to use the WebUserControlComponentActivator when instantiating our user controls.<br /><br />I didn't include the code for the ModelInspector in the article. Sorry for the confusion.

# MVP in ASP.Net

Left by mattberther.com at 9/22/2006 11:28 AM

# MVP Continued

Left by blogs.meetandplay.com at 8/19/2006 9:41 AM

Your comment:



 (will not be displayed)


 
 
 
Please add 6 and 2 and type the answer here:
 

Live Comment Preview:

 
«August»
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456