Per Request Activation With Ninject

posted on 10/14/08 at 08:00:00 pm by Joel Ross

As I've started to look into NHibernate and how to manage the NHibernate session, I quickly came across the "session per request" model as the recommended (or at least most popular) approach. I initially started out with a context-based storage and eventually refactored the code to be much simpler - the refactoring was very similar to what I did in my last post. By allowing my IoC container to manage the session's lifecycle, I no longer had to worry about it, and removed my reliance on a static class. As a result, I can easily change the session code without affecting any of the code that uses it.

When I started down this path, Nate Kohari warned me that Ninject's  OnePerRequestBehavior didn't seem to be working. He mentioned that he wondered how the Castle team did it, so I started digging. It turns out they did it with a combination of a life cycle management class (their equivalent of behaviors in Ninject) and an HTTP module. With that idea in mind, I dug into Ninject's code to see if I could get something working.

In the end, I got it working, but it's not a generic, "drop in" solution - it requires the registration of an HTTP module in your web.config file, so it's not as seamless as what Nate was going for. But it works, at least for my case. I created a class called CustomOnePerRequestBehavior that looks eerily similar to the original OnePerRequestBehavior! The main difference comes in the Resolve method. Rather than trying to handle the EndRequest event directly (which never actually fires) it just registers itself with the HTTP module:

   1:  public override object Resolve(IContext context)
   2:  {
   3:    Ensure.NotDisposed(this);
   4:   
   5:    lock (this)
   6:    {
   7:      if (ContextCache.Contains(context.Implementation))
   8:        return ContextCache[context.Implementation].Instance;
   9:   
  10:      ContextCache.Add(context);
  11:      context.Binding.Components.Get<IActivator>().Activate(context);
  12:   
  13:      RequestModule.RegisterForEviction(this);
  14:      return context.Instance;
  15:    }
  16:  }

This calls a static method on my request module:

   1:  internal static void RegisterForEviction(CustomOnePerRequestBehavior manager)
   2:  {
   3:    HttpContext context = HttpContext.Current;
   4:   
   5:    IList<CustomOnePerRequestBehavior> candidates = (IList<CustomOnePerRequestBehavior>)context.Items[PerRequestEvict];
   6:   
   7:    if (candidates == null)
   8:    {
   9:      candidates = new List<CustomOnePerRequestBehavior>();
  10:      context.Items[PerRequestEvict] = candidates;
  11:    }
  12:   
  13:    candidates.Add(manager);
  14:  }

This basically stores a list of items that should be taken care of at the end of a request, and stores it in the context.

Beside the static method, the request module also registers to listen for the EndRequest event. When that event fires, it grabs the list of items that should be taken care of at the end of the request, and calls the CleanUpInstances method on the stored instance (that was the other change to the original OnePerRequestBehavior - CleanUpInstances is now internal instead of private):

   1:  void context_EndRequest(object sender, EventArgs e)
   2:  {
   3:    HttpApplication application = (HttpApplication)sender;
   4:    IList<CustomOnePerRequestBehavior> candidates = (IList<CustomOnePerRequestBehavior>)application.Context.Items[PerRequestEvict];
   5:   
   6:    if (candidates != null)
   7:    {
   8:      foreach (CustomOnePerRequestBehavior candidate in candidates)
   9:      {
  10:        candidate.CleanUpInstances();
  11:      }
  12:   
  13:      application.Context.Items.Remove(PerRequestEvict);
  14:    }
  15:  }

And that's pretty much it. To use it, I can have a line like this in my module:

   1:  Bind<ISession>().ToProvider<SessionProvider>().Using<CustomOnePerRequestBehavior>();

I should note that I didn't really write any of my own code here. I used a combination of what the existing OnePerRequestBehavior already does, adding in what the Castle team did. Regardless of where the code came from, by doing this, I can now easily achieve session per request, which is what my goal was in the first place.

Tags: | |

Categories: Development, C#