Upgrading a Unity 1.2 custom build strategy to 2.0

Dec 1, 2010 at 6:36 PM

I have a custom extension I created for Unity 1.2. Basically what I am trying to achieve is for every call to Resolve for an interface inheiting from a certain base interface, I want to create the object with my own factory. I want that object then to be cached in the container with a container controlled lifetime manager. Here's the code I was using in Unity 1.2

public class FactoryMethodUnityExtension<T> : UnityContainerExtension
{
    private Func<Type,T> factory;
 
    public FactoryMethodUnityExtension(Func<Type,T> factory)
    {
        this.factory = factory;
    }
 
    protected override void Initialize()
    {
        var strategy = new CustomFactoryBuildStrategy<T>(factory, Context);
 
        Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);           
    }
}
 
public class CustomFactoryBuildStrategy<T> : BuilderStrategy
{
    private Func<Type,T> factory;
    private ExtensionContext baseContext;
 
    public CustomFactoryBuildStrategy(Func<Type,T> factory, ExtensionContext baseContext)
    {
        this.factory = factory;
        this.baseContext = baseContext;
    }
 
    public override void PreBuildUp(IBuilderContext context)
    {
        var key = (NamedTypeBuildKey)context.OriginalBuildKey;
 
        if (key.Type.IsInterface && typeof(T).IsAssignableFrom(key.Type))
        {
            object existing = baseContext.Locator.Get(key.Type);
            if (existing == null)
            {
                // create it
                context.Existing = factory(key.Type);
                 
                // cache it
                baseContext.Locator.Add(key.Type, context.Existing);
            }
            else
            {
                context.Existing = existing;
            }
        }
    }
}
However, in unity 2.0 this won't compile since the ExtensionContext no longer has a Locator. I can't use the container directly instead or I get a stack overflow exception.
How do I work round this for unity 2.0, or is there an easier way?
(if you are wondering why I need to do this, it is because our application has around 50 interfaces that all need to be created using .NET remoting. they all inherit from the same base interface, so I want to set the container up to work for them all without explicitly registering a factory method for each and every one).
thanks
Mark
Dec 2, 2010 at 5:05 AM
Edited Dec 2, 2010 at 5:06 AM

Instead of using the Locator, just create a policy to store the objects you've cached, add the policy to the PersistentPolicies collection if it's not already there, and put the cached object in there.

The primary reason the locator was removed was that it didn't actually add any capability, and it confused things about when to put stuff in a policy or in the locator. So I removed the confusion.

Actually, the logic you're describing doesn't match the code you posted - adding it to the locator doesn't set a container controlled lifetime, instead it holds onto the object with a weak reference, and it won't get disposed when the container does. Again, for the lifetime, you create a ContainerControlledLifetimeManager (which is a policy object), stick the object in it, and add it to the policy list.

Actually, now that I think about it a little more, I suspect that the custom policy discussed isn't needed at all. You just set up the lifetime manager, then the second time through the lifetime manager will be populated and the object will be pulled from there, and the rest of the buildup chain doesn't even execute.

 

 

 

Dec 2, 2010 at 10:40 AM

thanks, although I'm a little confused as to what exactly you are suggesting I do. I don't think I can create only a custom lifetime manager, since I need to itercept resolve for every interface that derives from a base type.

The two things I can't do within my existing extension are

1) having created my object, store it in the container e.g. container.RegisterInstance(derivedType, createdInstance)

2) on the second request, retrieve it from the container container.Resolve(derivedType)

Documentation on Unity 2.0 extensions seems to be thin on the ground - MSDN sends you back here, but the downloadable help file has no more info. Am I missing something?

Mark

Dec 2, 2010 at 11:49 AM

ok, I have a solution now but it includes a horrible hack to avoid a stack overflow exception:

    public class CustomFactoryBuildStrategy : BuilderStrategy
    {
        private ExtensionContext baseContext;
        private ICustomFactory factory;

        public CustomFactoryBuildStrategy(ICustomFactory factory, ExtensionContext baseContext)
        {
            this.factory = factory;
            this.baseContext = baseContext;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            var key = (NamedTypeBuildKey)context.OriginalBuildKey;
            
            if (factory.CanCreate(key.Type))
            {
                object existing = GetExistingObject(key.Type, context);
                if (existing == null)
                {
                    // create and cache it
                    context.Existing = factory.Create(key.Type);                   
baseContext.Container.RegisterInstance(type, existing, new ContainerControlledLifetimeManager());
                }
                else
                {
                    context.Existing = existing;
                }
            }
        }

        bool disableMe; // to avoid stack overflow

        private object GetExistingObject(Type type, IBuilderContext context)
        {
            if (disableMe) return null;
            IUnityContainer container = context.NewBuildUp<IUnityContainer>();
            try
            {
                disableMe = true;
                return container.Resolve(type);
            }
            finally
            {
                disableMe = false;
            }
        }
    }

Surely there is a better way than this to implement GetExistingObject. I tried ExecuteBuildUp on the strategy chain, but that also results in stack overflow

Mark

Dec 2, 2010 at 10:44 PM

You're right, there's a much easier way to do this. You need to drop down further into the internals, though.

You shouldn't need to implement GetExistingObject at all. The container will do the right thing for you. Look at what RegisterInstance is actually doing in the source code - it's taking the provided lifetime manager and putting it into the policy list. You can do exactly that yourself. Once it's in a lifetime manager, the container's standard lifetime logic is going to kick in, and your strategy (which is in the pipeline after the Lifetime stage) won't even be called - it got the object out of the lifetime manager and short-circuited the rest of the resolve logic.

I haven't actually tried this, but code along these lines should do what you want:

    public class CustomFactoryBuildStrategy : BuilderStrategy
    {
        public override void PreBuildUp(IBuilderContext context)
        {
            var key = (NamedTypeBuildKey)context.OriginalBuildKey;
            
            if (factory.CanCreate(key.Type) && context.Existing == null)
            {
                context.Existing = factory.Create(key.Type);
                var ltm = new ContainerControlledLifetimeManager();
                ltm.SetValue(context.Existing);

                // Add lifetime manager to container
                context.PersistentPolicies.Set<ILifetimePolicy>(ltm, new NamedTypeBuildKey(key.Type));
                // And add to LifetimeContainer so it gets disposed
                context.Lifetime.Add(ltm);

                // Short circuit the rest of the chain, object's already created
                context.BuildComplete = true;
            }
        }
     }

Should do the same thing you were doing, or at least push you in the right direction. Hope this helps,

 

-Chris

 

 

Dec 3, 2010 at 8:26 AM

thanks Chris,

that is very helpful. I had downloaded the Unity code and was working through it, but there is a lot to get your head round in there.

Your sample works perfectly, with one exception. We make a lot of use of Child containers (sometimes three levels deep), and my original Unity 1.2 extension let you fall through to the parent container rather than having each child container cache a new instance. Here's my two failing unit tests to show what I am trying to achieve:

 

        [Test]
        [Category("AutomaticTest")]
        public void SameCreatedFromTwoChildContainers()
        {
            var child1 = container.CreateChildContainer();
            var one1 = child1.Resolve<IOne>();
            Assert.IsNotNull(one1);
            AssertIsInstanceOf<One>(one1);

            var child2 = container.CreateChildContainer();
            var one2 = child2.Resolve<IOne>();
            Assert.IsNotNull(one2);
            AssertIsInstanceOf<One>(one2);

            Assert.AreSame(one1, one2);
        }
        [Test]
        [Category("AutomaticTest")]
        public void WhenCreatedInChildUsesBaseCache()
        {
            var child = container.CreateChildContainer();
            var one = child.Resolve<IOne>();
            Assert.IsNotNull(one);
            AssertIsInstanceOf<One>(one);

            var oneBase = container.Resolve<IOne>();
            Assert.IsNotNull(oneBase);
            AssertIsInstanceOf<One>(oneBase);
            Assert.AreSame(one, oneBase);
        }

I tried to see if I could exclude my custom build strategy from child containers, but in the Context.ChildContainerCreated I couldn't see a way to remove a strategy from the e.ChildContext.Strategies collection. (you can clear and re-add them all, but you need to know what build stage each one belongs to).

Alternatively, my strategy could try to find its way to the root container's IBuilderContext and set the persistent policy there, but I haven't found a way to get to the parent IBuilderContext yet

Anyway, sorry to keep pestering you, but any pointers in the right direction would be appreciated.

Mark

 

Dec 3, 2010 at 9:45 PM

So is it safe to say you always want the caching to happen in the root container, or that you want the caching to happen in some parent container that may or may not be the root?

Policy lists have a hierarchical structure - each one has a parent. However you're right to be confused in that the structure isn't exposed directly to the users - there's no way to walk up to a policy list's parent. However, when you get a policy object from the policy list, you can find out which policy list it actually came from.

This allows you to do a little bit of a cheat. Define a "marker" policy type. It'll also need to expose a LifetimeContainer (you'll see why later).

public class ParentMarkerPolicy : IBuilderPolicy
{
    private ILifetimeContainer lifetime;

    public ParentMarkerPolicy(ILifetimeContainer lifetime)
    {
        this.lifetime = lifetime;
    }

    public void AddToLifetime(object o)
    {
        lifetime.Add(o);
    }
}


In your Initialize method for your extension, add this policy to your container.

protected override void Initialize()
{
    Context.Strategies.AddNew<CustomStrategy>(UnityBuildStage.PreCreation);

    Context.Policies.Set<ParentMarkerPolicy>(new ParentMarkerPolicy(Context.Lifetime),
        new NamedTypeBuildKey<ParentMarkerPolicy>());
}

Then, in your strategy, look for this marker policy. Use the source of that policy to store the new lifetime manager.

if (factory.CanCreate(key.Type) && context.Existing == null)
{
    context.Existing = factory.Create(key.Type);
    var ltm = new ContainerControlledLifetimeManager();
    ltm.SetValue(context.Existing);

    // Find the container to add this to
    IPolicyList parentPolicies;
    var parentMarker = context.Policies.Get<ParentMarkerPolicy>(new NamedTypeBuildKey<ParentMarkerPolicy>(out parentPolicies));

    // TODO: add error check - if policy is missing, extension is misconfigured

    // Add lifetime manager to container
    parentPolicies.Set<ILifetimePolicy>(ltm, new NamedTypeBuildKey(key.Type));
    // And add to LifetimeContainer so it gets disposed
    parentMarker.AddToLifetime(ltm);

    // Short circuit the rest of the chain, object's already created
    context.BuildComplete = true;
}

That should do the trick. If you want to put the cached version somewhere else in the container hierarchy, you can do this - just add a new copy of the parent marker policy to the policy list in that container. The PolicyList always finds the policy that matches the type and key requested in the list closest to the one you're requesting from.

Hope this helps,

 

-Chris

 

Dec 3, 2010 at 10:15 PM

thanks Chris, you have been so helpful and all my unit tests are passing now. There's one typo in your code, should be:

var parentMarker = context.Policies.Get<ParentMarkerPolicy>(new NamedTypeBuildKey<ParentMarkerPolicy>(), out parentPolicies);

I also think this solution seems nicer than the approach taken by various AutoMockingContainer extensions I looked at while trying to solve this.

I had been experimenting with InjectionFactory as a possible fallback if I couldn't get this working. With Unity 2, it's really easy to set up, although I will stick with the extension as it means I don't need to know what assemblies the interfaces I want to resolve reside in:


foreach (var type in this.GetType().Assembly.GetTypes())
{
if (factory.CanCreate(type))
    {   
        container.RegisterType(type, new ContainerControlledLifetimeManager(), new InjectionFactory((c, t, s) => factory.Create(t)));
    }
}

Mark