bling.github.io

This blog has relocated to bling.github.io.

Friday, May 14, 2010

Contextual Lifestyle with Castle Windsor

EDIT: As of version 3, scoped lifestyles are now a first class citizen supported out of the box (http://docs.castleproject.org/Windsor.Whats-New-In-Windsor-3.ashx)
EDIT: A much better implementation can be found at https://github.com/castleprojectcontrib/Castle.Windsor.Lifestyles

IMO, one of the big missing features of Castle Windsor is that it doesn’t come with a built-in way for dealing with contextual lifestyles.  It handles transients and singletons fairly well, but once you get to other lifestyles it’s pretty heavily dependent on having some “state manager” handling the instances.  For example, PerWebRequest uses the HttpContext, PerThread uses thread static variables, etc.
Contextual lifestyles is one of those things where it doesn’t seem all that useful at first, and then when you see the possibilities it’s like getting hit with a huge truck.
A question was posted to the Castle Google Group recently, which I follow, which illustrates a relatively common example of why someone would want to have a contextual lifestyle.  Basically, you have a whole bunch of components you want to resolve, but only within a context.
Here’s some boiler plate code of the domain model:
public interface IRepository { ISession Session { get; } }
public interface ISession : IDisposable { bool IsDisposed { get; } }
public class Session : ISession
{
    public bool IsDisposed { get; set; }
    public void Dispose() { IsDisposed = true; }
}
public class Repository1 :IRepository
{
    public ISession Session { get; private set; }
    public Repository1(ISession session){ Session = session; }
}
public class Repository2 : IRepository
{
    public ISession Session { get; private set; }
    public Repository2(ISession session){ Session = session; }
}
public class Model1
{
    public IRepository First { get; private set; }
    public IRepository Second { get; private set; }
    public Model1(IRepository first, IRepository second) { First = first; Second = second; }
}
public class Model2
{
    public IRepository Second { get; private set; }
    public Model2(IRepository second) { Second = second; }
}
And here’s the unit test I want to pass:
[Test]
        public void ResolutionsByContext()
        {
            IWindsorContainer root = new WindsorContainer();
            root.Register(Component.For<Model1>().LifeStyle.Transient,
                          Component.For<Model2>().LifeStyle.Transient,
                          Component.For<IRepository>().ImplementedBy<Repository1>().LifeStyle.Transient,
                          Component.For<IRepository>().ImplementedBy<Repository2>().LifeStyle.Transient,
                          Component.For<ISession>().ImplementedBy<Session>().LifeStyle.PerContextScope());

            Model1 model1;
            Model2 model2;
            ISession session1, session2;
            using (var context1 = root.BeginLifetimeScope())
            {
                model1 = context1.Resolve<Model1>();
                session1 = model1.First.Session;
                Assert.AreSame(model1.First.Session, model1.Second.Session);
                Assert.AreSame(context1.Resolve<ISession>(), context1.Resolve<ISession>());

                using (var context2 = root.BeginLifetimeScope())
                {
                    model2 = context2.Resolve<Model2>();
                    session2 = model2.Second.Session;
                    Assert.AreNotSame(model1.First.Session, model2.Second.Session);

                    var anotherModel2 = context2.Resolve<Model2>();
                    Assert.AreSame(anotherModel2.Second.Session, model2.Second.Session);

                    Assert.AreSame(session2, context2.Resolve<ISession>());
                    Assert.AreNotSame(context1.Resolve<ISession>(), context2.Resolve<ISession>());
                }
                Assert.IsTrue(session2.IsDisposed);
                Assert.IsFalse(session1.IsDisposed);
            }
            Assert.IsTrue(session1.IsDisposed);
        }

I copied the name BeginLifetimeScope from Autofac, which inherently supports contextual scopes as a first-class citizen (of which the test passes).  The question now, is how do we get Castle Windsor to do the same?
Initially, I took a look at ISubDependencyResolver and caching variables.  Unfortunately, this didn’t work too well because sub resolvers never got hit if they were resolved from the container directly.
The next step I tried was with lifestyle managers, but alas, the CreationContext was always transient and I was unable to store any state that distinguished between different context resolutions.
After digging deeper into the Windsor codebase and getting into the subsystems and handlers, I found a solution that seems to work.  It passes the test above, but that’s about it.  Test well if you’re gonna use this in production code!!!
Here goes!
First, you have a lifestyle manager to distinguish between other lifestyles.
public class ContextualLifestyleManager : AbstractLifestyleManager
    {
        private object instance;
        public override object Resolve(CreationContext context)
        {
            return instance ?? (instance = base.Resolve(context));
        }
        public override void Dispose()
        {
        }
    }
And finally, the magic happens with this:
public static class ContextualExtensions
    {
        public static ComponentRegistration<T> PerContextScope<T>(this LifestyleGroup<T> group)
        {
            return group.Custom<ContextualLifestyleManager>();
        }
        public static IWindsorContainer BeginLifetimeScope(this IWindsorContainer parent)
        {
            var child = new WindsorContainer();
            var ss = (INamingSubSystem)parent.Kernel.GetSubSystem(SubSystemConstants.NamingKey);
            foreach (var handler in ss.GetHandlers())
            {
                if (handler.ComponentModel.CustomLifestyle == typeof(ContextualLifestyleManager))
                {
                    child.Kernel.AddCustomComponent(handler.ComponentModel);
                }
            }
            parent.AddChildContainer(child);
            return child;
        }
    }
First method is just a helper method to be a little more fluent in the registration for when you want many things to have contextual lifestyle.  The second method is the guts.  Long story short, we create a child container, and duplicate all component models of contextual lifestyle.  Thus, whenever components are resolved, the “override” is found in the child and resolved.  Anything else will be found in the parent.
I was initially pretty happy with this, until I profiled the performance.  With Autofac, creating and disposing 100,000 contexts took 5ms on my computer.  Doing the same with with Windsor took 3.8 seconds.  Out of curiosity, I profiled again, but this time just creating child containers without copying handlers down: 1.9 seconds.  So while this implementation works, it’s not as performant as I’d like it to be….
Maybe I’ll come up with another solution, but for now if the performance is acceptable maybe this would be useful for others!

3 comments:

Krzysztof Koźmic (2) said...

No wonder it is taking so long, you're doing it in very heavyweight fashion.


It can be done w/o nested containers, much more lightweight.

Krzysztof Koźmic (2) said...

That's my quick and dirty impl based on per-web-request lifestyle that is far more lightweight and should have similar perf characteristics to other lifestyles

http://gist.github.com/400979
http://gist.github.com/400980

bling said...

LOL I need to check these comments more often. Thanks for responding!