Sunday, July 5, 2009

Castle: PerWebRequestLifeStyle won’t work from Application_Start

Update: Please read the follow up post on this topic.

I’m using the Castle project to implement dependency injection and inversion of control in my web applications.
Castle supports creating objects on a per-request basis which is a big help in keeping the number of objects created per request to a minimum.

I ran into a problem with this however. The PerWebRequestLifeStyle is implemented using an HttpModule to make sure objects are evicted at the end of a request. This works well except when using Castle from the Application_Start method (or the Init method for that matter).

The Application_Start method is invoked before any modules are initialized for the application and therefore any objects registered with Castle to use the PerWebRequestLifestyle cannot be used in Application_Start.

The problem manifests itself by reporting the module is probably not configured:

Looks like you forgot to register the http module Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule
Add '<add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.MicroKernel" />' to the <httpModules> section on your web.config.

Similar problems occur when performing tests outside the web environment because, well, there is no web request obviously.

Workaround
I’ve implemented a workaround by customizing the implementation of the lifestyle manager. This work-around falls back to the TransientLifeStyleManager in case the web context is not available. T
his enables both testing and application startup to use objects managed per web request.

[Serializable] 
public class PerWebRequestLifestyleManager : Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleManager 
{ 
  public PerWebRequestLifestyleManager() 
  { 
    _fallback = new TransientLifestyleManager(); 
  }

  public override object Resolve( CreationContext context ) 
  { 
    HttpContext current = HttpContext.Current;
    if ( null == current || current.ApplicationInstance == null )    
    {
      // fall back to transient behaviour if not in web context 
      return _fallback.Resolve( context ); 
    } 
    else 
    { 
      return base.Resolve( context ); 
    } 
  }

  public override void Dispose() 
  { 
    _fallback.Dispose(); 
    base.Dispose(); 
  }   
  
  public override void Init( IComponentActivator componentActivator, IKernel kernel, Castle.Core.ComponentModel model ) 
  { 
    base.Init( componentActivator, kernel, model ); 
    _fallback.Init( componentActivator, kernel, model ); 
  }   
  
  private TransientLifestyleManager _fallback; 
}

To make sure Castle uses this custom implementation I’m using a custom AttributeFacility:

internal class AttributeFacility : AbstractFacility
{
   protected override void Init()
   {
      Kernel.ComponentModelCreated += kernel_ComponentModelCreated;
   }

   void kernel_ComponentModelCreated( ComponentModel model )
   {
      var attributes = model.Implementation.GetCustomAttributes( typeof( Castle.Core.LifestyleAttribute ), false );
      if ( attributes.Length > 0 )
      {
         var attr = attributes[ 0 ] as LifestyleAttribute;
         if ( null != attr )
         {
            switch ( attr.Lifestyle )
            {
               case LifestyleType.PerWebRequest:
                  model.CustomLifestyle = typeof( Alanta.Services.PerWebRequestLifestyleManager );
                  model.LifestyleType = LifestyleType.Custom;
                  break;
               default:
                  break;
            }
         }
      }
   }    
}

I usually hook up the facility from code, like this:

var container = new WindsorContainer();
container.AddFacility<AttributeFacility>();

Update : this solution does not work on IIS7 in integrated pipeline mode. In this mode both HttpContext.Current and HttpContext.Current.ApplicationInstance are set so the detection method in the code above does not work.
I haven’t found a clean solution for this yet; I’ve had to resort to setting a flag in Application_Start that controls the behavior of the PerWebRequestLifestyleManager. Check out the demo code to see how this works.

Update : There is a discussion on this subject at the Castle Project Users group.

Update : Download the source code for this solution. The zip contains an ASP.NET MVC 1 demo application (VS 2008). The code in this