"Cascading" PerResolveLifetimeManager?

Feb 18, 2011 at 8:06 AM

Hi,

 

I have what seems to be an unusual case. I am resolving a class via the standard Resolve<T>() method call. However, where it gets strange is that during the scope of the constructor of this object, I call a ServiceLocator interface to the container to construct another object. There is also the possibility of the the ServiceLocator being invoked multiple times to build up more objects (all during the construction of the first "parent" object). [Yes, I know this isn't "best practice" for object resolution, but I am working with XAML views, view-models, sub views, and sub-view-models, and this gives the most testable way of creating all the objects properly.]

 

Now I want to have a dependency object instance shared across all the nested calls to the container. This is very similar in nature to "PerResolveLifetimeManager", but it doesn't quite work as I expected. The PerResolveLifetimeManager resolves the same dependency for any object I specify in my parent class, but any of the "cascading" calls to the container don't return the same instance of the dependency.

 

Is it possible to know if you are currently inside the scope of another "Resolve()" method call (and thus provide the dependency to the cascading object creation)?

 

Cheers,

-Brad

Feb 21, 2011 at 1:05 AM

Rather than injecting and then calling back into the container directly, I'd suggest splitting out an explicit factory for generating those other things you need. The factory can then itself be injected with the container and make the calls.

This has a couple of advantages. It reduces coupling to the container, for one. The factory makes the dependencies more explicit. And the factory itself can be injected with the overridden services when it's created, then hold onto them and re-use them as overrides when it calls back into the container.

Hope this gives you some useful ideas,

-Chris

Feb 21, 2011 at 1:15 AM

Thanks for the idea Chris. I have a working solution now, but i will consider if the option you have suggested is more intuitive than the solution I have managed to develop.

In case anyone is wanting a similar solution, this is what I did. First, I created and registered a custom strategy:

 public class CascadingResolveLifetimeStrategy : BuilderStrategy, IRequiresRecovery
    {
        [ThreadStatic]
        static int count = 0;

        public override void PreBuildUp(IBuilderContext context)
        {
            // Make sure we register this class so that it cleans up the count in the case of an exception
            context.RecoveryStack.Add(this);
            count++;
        }

        public override void PostBuildUp(IBuilderContext context)
        {
            if (count > 0)
                count--;

            if (count == 0)
                CascadingResolveLifetimeManager.ResetCache();
        }

        #region IRequiresRecovery Members

        public void Recover()
        {
            if (count > 0)
                count--;

            if (count == 0)
                CascadingResolveLifetimeManager.ResetCache();
        }

        #endregion
    }

Then I created a custom LifetimeManager:

public class CascadingResolveLifetimeManager : LifetimeManager
    {
        [ThreadStatic]
        private static Dictionary<Guid, object> cache;
        private Guid key;

        public CascadingResolveLifetimeManager()
        {
            key = Guid.NewGuid();
        }

        public override object GetValue()
        {
            EnsureCacheExists();

            object result;
            cache.TryGetValue(key, out result);
            return result;
        }

        public override void SetValue(object newValue)
        {
            EnsureCacheExists();

            cache.Add(key, newValue);
        }

        public override void RemoveValue()
        {

        }

        public static void ResetCache()
        {
            if (cache != null)
            {
                cache = null;
            }
        }

        private static void EnsureCacheExists()
        {
            if (cache == null)
                cache = new Dictionary<Guid, object>();
        }
    }

This is basically a combination of the PerThreadLifetimeManager, the PerResolveLifetimeManager, and a simple mechanism to see what "level" I am at when resolving.

I'd be happy to hear if there any major issues with this implementation - I feel I don't understand Unity enough to be doing things like this. :-)