Monday, March 21, 2011

ASP.NET MVC 3 Dependency Injection using Windsor Castle, Singleton Lifestyle and object not set to an instance

Dependency Injection and Inversion of Control is an increasingly common and popular design principle in ASP.NET MVC.

However for the uninitiated (and even for those how have worked with it for some time) you can run into unexpected errors, especially when mixing Lifestyles for different Components on larger projects where you have multiple container installers/initializers.

I will here present a very obvious case that nevertheless had me and another developer spending a few hours trying to figure out what was wrong. This bug presented here are especially difficult to track down when you are doing frequent builds on a development machine and that build process causes the IIS application pool(s) to recycle, effectively resetting the IoC container.

I will be using Castle Windsor as the IoC (Inversion of Control) container in this posting.

The zipped solution can be downloaded here IocLifestyles.zip

First of all we will need to start with a new standard empty ASP.MVC NET 3.0 site/project and add a reference to Castle.Windsor. This reference can be added using NuGet or by downloading the source or binary libraries at the 
Castle Windsor website.

Secondly we need to tell ASP.NET MVC that we do not want to use the DefaultControllerFactory to create controller instances but that we want to use our own which in turn uses Windsor to provide the dependencies the various controllers need.

I do this in the Global.asax file by adding the following lines in the Application_Start method.


// Using WindsorControllerFactory rather then DependencyResolver
// http://bradwilson.typepad.com/blog/2010/07/service-location-pt1-introduction.html (read comments)
// http://bradwilson.typepad.com/blog/2010/10/service-location-pt5-idependencyresolver.html (read comments)
// http://bradwilson.typepad.com/blog/2010/10/service-location-pt10-controller-activator.html (read comments)
// http://mikehadlow.blogspot.com/2011/02/mvc-30-idependencyresolver-interface-is.html
// http://kozmic.pl/2010/08/19/must-windsor-track-my-components/
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(applicationWideWindsorContainer));

// Initialize / install components in container
applicationWideWindsorContainer.Install(new WindsorInstaller());
As well as the the field

WindsorContainer applicationWideWindsorContainer = new WindsorContainer();
This leads to our Global.asax files looking like this

using System.Web.Mvc;
using System.Web.Routing;
using Castle.Windsor;

namespace IocLifestyles
{
 // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
 // visit http://go.microsoft.com/?LinkId=9394801

 public class MvcApplication : System.Web.HttpApplication
 {
  public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  {
   filters.Add(new HandleErrorAttribute());
  }

  public static void RegisterRoutes(RouteCollection routes)
  {
   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

   routes.MapRoute(
     "Default", // Route name
     "{controller}/{action}/{id}", // URL with parameters
     new { controller = "Default", action = "Index", id = UrlParameter.Optional } // Parameter defaults
   );

  }

  WindsorContainer applicationWideWindsorContainer = new WindsorContainer();

  protected void Application_Start()
  {
   AreaRegistration.RegisterAllAreas();

   RegisterGlobalFilters(GlobalFilters.Filters);
   RegisterRoutes(RouteTable.Routes);

   // Using WindsorControllerFactory rather then DependencyResolver
   // http://bradwilson.typepad.com/blog/2010/07/service-location-pt1-introduction.html (read comments)
   // http://bradwilson.typepad.com/blog/2010/10/service-location-pt5-idependencyresolver.html (read comments)
   // http://bradwilson.typepad.com/blog/2010/10/service-location-pt10-controller-activator.html (read comments)
   // http://mikehadlow.blogspot.com/2011/02/mvc-30-idependencyresolver-interface-is.html
   // http://kozmic.pl/2010/08/19/must-windsor-track-my-components/
   ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(applicationWideWindsorContainer));

   // Initialize / install components in container
   applicationWideWindsorContainer.Install(new WindsorInstaller());
  }
 }
}

We now need to look closer at 2 classes, namely the WindsorControllerFactory and the WindsorInstaller.

The responsibility of the WindsorControllerFactory is to provide all the objects to the controller which it depends on to perform it's logic. This is passed in to the controllers constructor. This is done using Constructor Injection (you can see a lot of good tips, thoughts and recommendations at Mark Seeman's blog).

using System;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.Windsor;

namespace IocLifestyles
{
 public class WindsorControllerFactory : DefaultControllerFactory
 {
  private readonly IWindsorContainer windsorContainer;

  public WindsorControllerFactory(IWindsorContainer windsorContainer)
  {
   this.windsorContainer = windsorContainer;
  }

  protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
  {
   return windsorContainer.Resolve(controllerType) as IController;
  }

  public override void ReleaseController(IController controller)
  {
   var disposableController = controller as IDisposable;
   if (disposableController != null)
   {
    disposableController.Dispose();
   }

   windsorContainer.Release(controller);
  }
 }
}
Now we need to register the classes which we are going to use through out our application with the IoC container and we do this by implementing the IWindsorInstaller interface.

Please note the Lifestyles for the different components, since this is the key to the bug I am going to illustrate.

using System.Web.Mvc;
using Castle.Facilities.FactorySupport;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System.Web;

namespace IocLifestyles
{
 public class WindsorInstaller : IWindsorInstaller
 {
  public void Install(IWindsorContainer container, IConfigurationStore store)
  {
   // Register all controllers from this assembly
   container.Register(
    AllTypes.FromThisAssembly()
    .BasedOn<Controller>()
    .Configure(c => c.LifeStyle.PerWebRequest)
   );

   // Register HttpContext(Base) and HttpRequest(Base) so it automagically can be injected using IoC
   container.AddFacility<FactorySupportFacility>();
   container.Register(Component.For<HttpRequestBase>().LifeStyle.PerWebRequest
      .UsingFactoryMethod(() => new HttpRequestWrapper(HttpContext.Current.Request)));
   container.Register(Component.For<HttpContextBase>().LifeStyle.PerWebRequest
     .UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));

   // Respository and Service registrations
   container.Register(Component.For<ISomeRepository>().ImplementedBy<SomeRepository>());
   container.Register(Component.For<ISessionValidator>().ImplementedBy<SessionValidator>().LifeStyle.PerWebRequest);
  }
 }
}

Looking closer at the 2 classes that we have registered in the WindsorInstaller: SomeRepository and SessionValidator.

namespace IocLifestyles
{
 public interface ISomeRepository
 {
  SessionToken GetSessionToken();
 }

 public class SomeRepository : ISomeRepository
 {
  private readonly ISessionValidator sessionValidator;

  public SomeRepository(ISessionValidator sessionValidator)
  {
   this.sessionValidator = sessionValidator;
  }

  public SessionToken GetSessionToken()
  {
   return sessionValidator.ValidateSession();
  }
 }
}
using System.Net;
using System.Web;

namespace IocLifestyles
{
 public interface ISessionValidator
 {
  SessionToken ValidateSession();
 }
 public class SessionValidator : ISessionValidator
 {
  private readonly HttpContextBase httpContextBase;

  public SessionValidator(HttpContextBase httpContextBase)
  {
   this.httpContextBase = httpContextBase;
  }

  public SessionToken ValidateSession()
  {
   // Do some validation here
   var sessionToken = new SessionToken
             {
              IpAddress = IPAddress.Parse(httpContextBase.Request.UserHostAddress),
              IsValid = true
             };

   return sessionToken;
  }
 }
}
The final 2 pieces needed to actually see something in the browser is the DefaultController
using System.Web.Mvc;

namespace IocLifestyles.Controllers
{
 public class DefaultController : Controller
 {
  private readonly ISomeRepository someRepository;

  // Constructor Injection
  public DefaultController(ISomeRepository someRepository)
  {
   this.someRepository = someRepository;
  }

  public ActionResult Index()
  {
   ViewData.Model = someRepository.GetSessionToken();
   return View();
  }
 }
}

and the Views/Default/Index.cshtml view.

@model IocLifestyles.SessionToken
@{
 ViewBag.Title = "Index";
}
<h2>
 SessionToken IpAddress: @Model.IpAddress.ToString()</h2>

This is a rather contrived example, but please bear with me for the sake of demonstration purposes.

What is interesting however is what happens when we run the application the first time and contrast this to what happens when we run it a second time without rebuilding the code.

First time:


Second and subsequent times:



Playing around with this you will notice that you can display the page one time after a build, this is giving us an indication of what is wrong.


Below you can see the full stack trace with all details
Server Error in '/' Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

Line 20:   {
Line 21:    // Do some validation here
Line 22:    var sessionToken = new SessionToken
Line 23:              {
Line 24:               IpAddress = IPAddress.Parse(httpContextBase.Request.UserHostAddress),


Source File: D:\Users\kst\Visual Studio 2010\projects\IocLifestyles\IocLifestyles\SessionValidator.cs    Line: 22

Stack Trace:

[NullReferenceException: Object reference not set to an instance of an object.]
   Microsoft.VisualStudio.WebHost.Connection.get_RemoteIP() +0
   Microsoft.VisualStudio.WebHost.Request.GetRemoteAddress() +65
   System.Web.HttpRequestWrapper.get_UserHostAddress() +22
   IocLifestyles.SessionValidator.ValidateSession() in D:\Users\kst\Visual Studio 2010\projects\IocLifestyles\IocLifestyles\SessionValidator.cs:22
   IocLifestyles.SomeRepository.GetSessionToken() in D:\Users\kst\Visual Studio 2010\projects\IocLifestyles\IocLifestyles\SomeRepository.cs:19
   IocLifestyles.Controllers.DefaultController.Index() in D:\Users\kst\Visual Studio 2010\projects\IocLifestyles\IocLifestyles\Controllers\DefaultController.cs:17
   lambda_method(Closure , ControllerBase , Object[] ) +96
   System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +17
   System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +208
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +27
   System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +55
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +263
   System.Web.Mvc.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14() +19
   System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +191
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +343
   System.Web.Mvc.Controller.ExecuteCore() +116
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +97
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
   System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +62
   System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +50
   System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
   System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +22
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +60
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8862285
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184


Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.208

The reason this is failing is the mixing of Lifestyles in the WindsorInstaller. Lets bring up that particular code again.

using System.Web.Mvc;
using Castle.Facilities.FactorySupport;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System.Web;

namespace IocLifestyles
{
 public class WindsorInstaller : IWindsorInstaller
 {
  public void Install(IWindsorContainer container, IConfigurationStore store)
  {
   // Register all controllers from this assembly
   container.Register(
    AllTypes.FromThisAssembly()
    .BasedOn<Controller>()
    .Configure(c => c.LifeStyle.PerWebRequest)
   );

   // Register HttpContext(Base) and HttpRequest(Base) so it automagically can be injected using IoC
   container.AddFacility<FactorySupportFacility>();
   container.Register(Component.For<HttpRequestBase>().LifeStyle.PerWebRequest
      .UsingFactoryMethod(() => new HttpRequestWrapper(HttpContext.Current.Request)));
   container.Register(Component.For<HttpContextBase>().LifeStyle.PerWebRequest
     .UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));

   // Respository and Service registrations
   container.Register(Component.For<ISomeRepository>().ImplementedBy<SomeRepository>());
   container.Register(Component.For<ISessionValidator>().ImplementedBy<SessionValidator>().LifeStyle.PerWebRequest);
  }
 }
}

The default lifestyle is Singleton. Omitting the lifestyle on ISomeRepository will register it as a Lifestyle.Singleton. SomeRepository will be instantiated one time by Windsor and then cached internally for future use. This means that even though all the other registered Components are Lifestyle.PerWebRequest, ISomeRepository will not be able to benefit from this.

There are no warnings that helps you if you mix Lifestyles in your installers, and especially in larger projects, this can be a challenge when you start tweaking for memory/performance reasons.
You need to know how all the dependencies work, and how they are used, to be able tweak Lifestyles for components. If you do not have this knowledge the risk is very high that you will introduce bugs. My suggestion especially if you are new to ASP.NET MVC leave Lifestyles to Lifestyle.PerWebRequest for all components, unless it is very obvious that this is a real singleton component. Only start tweaking Lifestyles if performance or memory consumption becomes a problem.

Changing the installer to the following solves the problem.

using System.Web.Mvc;
using Castle.Facilities.FactorySupport;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System.Web;

namespace IocLifestyles
{
 public class WindsorInstaller : IWindsorInstaller
 {
  public void Install(IWindsorContainer container, IConfigurationStore store)
  {
   // Register all controllers from this assembly
   container.Register(
    AllTypes.FromThisAssembly()
    .BasedOn<Controller>()
    .Configure(c => c.LifeStyle.PerWebRequest)
   );

   // Register HttpContext(Base) and HttpRequest(Base) so it automagically can be injected using IoC
   container.AddFacility<FactorySupportFacility>();
   container.Register(Component.For<HttpRequestBase>().LifeStyle.PerWebRequest
      .UsingFactoryMethod(() => new HttpRequestWrapper(HttpContext.Current.Request)));
   container.Register(Component.For<HttpContextBase>().LifeStyle.PerWebRequest
     .UsingFactoryMethod(() => new HttpContextWrapper(HttpContext.Current)));

   // Respository and Service registrations
   container.Register(Component.For<ISomeRepository>().ImplementedBy<SomeRepository>().LifeStyle.PerWebRequest);
   container.Register(Component.For<ISessionValidator>().ImplementedBy<SessionValidator>().LifeStyle.PerWebRequest);
  }
 }
}
Post a Comment