IoC.GetInstance Question

Jul 27, 2010 at 9:44 PM

I'm trying to follow the demo from MIX and apply it using the new version of CM.  I need to get the instance of the my shell so that I can change screens.

My shell view model is set up like this

PaidTimeOffShellViewModel : Conductor<IScreen>

But when I try to change screens using this code

IoC.Get<PaidTimeOffShellViewModel>().ActivateItem(new PaidTimeOffItemsViewModel());

the code appears to get a new instance of the shell instead of the existing one.  I see that there's a GetInstance on the new version if IoC and maybe that's what I should be using, but I'm not sure what to use for the parameters.

 

Thanks,

Brandon

Jul 28, 2010 at 12:19 AM
Edited Jul 28, 2010 at 1:07 AM
Most of the time, you should use something like IoC.Get<MyShellClass>().
GetInstance delegate is intended to customize the IoC behaviour. The default implementation of IoC uses no Dependency Inversion container, so you simply get a fresh instance each time you call it, which is not the intended result for the root ViewModel (shell).
To solve this you can either :
- use a DI container and forward IoC calls to it (you can follow Rob's guide to customize bootstrapper: http://devlicio.us/blogs/rob_eisenberg/archive/2010/07/08/caliburn-micro-soup-to-nuts-pt-2-customizing-the-bootstrapper.aspx).
- tweak IoC implementation setting the IoC.GetInstance delegate to a custom implementation, and cache a "singleton" instance of your root model class.

Does it helps?

In my fork of repository (http://caliburnmicro.codeplex.com/SourceControl/network/Forks/marcoamendola/caliburnmicromarcoamendolafork) you can find the GameLibrary application adapted to Caliburn Micro.
Jul 29, 2010 at 2:45 PM

Thanks for the reply.  I'm glad you metioned that you have a fork with the Game Library using an updated version of CM.  It's helping me understand the code more.  We are planning to modify our bootstrapper to have the code below.

        private static List<object > instanceCache = new List<object>();

        protected override object GetInstance(Type serviceType, string key)
        {
            object retVal;

            var instances = from obj in instanceCache
                              where obj.GetType() == serviceType
                              select obj;
            if (instances.Count() == 0)
            {
                retVal = Activator.CreateInstance(serviceType);
                instanceCache.Add(retVal);
            }
            else
                retVal = instances.First();

            return retVal;
        }

Can you think of any potential problems using this approach?

Coordinator
Jul 29, 2010 at 2:56 PM
Edited Jul 29, 2010 at 2:58 PM

It looks like you are trying to create your own container where everything is a singleton instance. Below is an implementation of a simple IoC Container I wrote for WP7. It actually does quite a bit. You may find it useful:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    public class SimpleContainer 
    {
        readonly List<ContainerEntry> entries = new List<ContainerEntry>();

        public void RegisterInstance(Type service, string key, object implementation)
        {
            RegisterHandler(service, key, () => implementation);
        }

        public void RegisterPerRequest(Type service, string key, Type implementation)
        {
            RegisterHandler(service, key, () => BuildInstance(implementation));
        }

        public void RegisterSingleton(Type service, string key, Type implementation)
        {
            object singleton = null;
            RegisterHandler(service, key, () => singleton ?? (singleton = BuildInstance(implementation)));
        }

        public void RegisterHandler(Type service, string key, Func<object> handler)
        {
            GetOrCreateEntry(service, key).Add(handler);
        }

        public object GetInstance(Type service, string key)
        {
            var entry = GetEntry(service, key);
            if(entry != null)
                return entry.Single()();

            if(typeof(Delegate).IsAssignableFrom(service))
            {
                var typeToCreate = service.GetGenericArguments()[0];
                var factoryFactoryType = typeof(FactoryFactory<>).MakeGenericType(typeToCreate);
                var factoryFactoryHost = Activator.CreateInstance(factoryFactoryType);
                var factoryFactoryMethod = factoryFactoryType.GetMethod("Create");
                return factoryFactoryMethod.Invoke(factoryFactoryHost, new object[] { this });
            }
            else if(typeof(IEnumerable).IsAssignableFrom(service))
            {
                var listType = service.GetGenericArguments()[0];
                var instances = GetAllInstances(listType).ToList();
                var array = Array.CreateInstance(listType, instances.Count);

                for(var i = 0; i < array.Length; i++)
                {
                    array.SetValue(instances[i], i);
                }

                return array;
            }

            return null;
        }

        public IEnumerable<object> GetAllInstances(Type service)
        {
            var entry = GetEntry(service, null);
            return entry != null ? entry.Select(x => x()) : new object[0];
        }

        ContainerEntry GetOrCreateEntry(Type service, string key)
        {
            var entry = GetEntry(service, key);
            if (entry == null)
            {
                entry = new ContainerEntry { Service = service, Key = key };
                entries.Add(entry);
            }

            return entry;
        }

        ContainerEntry GetEntry(Type service, string key)
        {
            return service == null
                ? entries.Where(x => x.Key == key).FirstOrDefault()
                : entries.Where(x => x.Service == service && x.Key == key).FirstOrDefault();
        }

        object BuildInstance(Type type)
        {
            var args = DetermineConstructorArgs(type);
            return ActivateInstance(type, args);
        }

        protected virtual object ActivateInstance(Type type, object[] args)
        {
            return args.Length > 0 ? Activator.CreateInstance(type, args) : Activator.CreateInstance(type);
        }

        object[] DetermineConstructorArgs(Type implementation)
        {
            var args = new List<object>();
            var constructor = SelectEligibleConstructor(implementation);

            if (constructor != null)
                args.AddRange(constructor.GetParameters().Select(info => GetInstance(info.ParameterType, null)));

            return args.ToArray();
        }

        static ConstructorInfo SelectEligibleConstructor(Type type)
        {
            return (from c in type.GetConstructors()
                    orderby c.GetParameters().Length descending
                    select c).FirstOrDefault();
        }

        class ContainerEntry : List<Func<object>>
        {
            public string Key;
            public Type Service;
        }

        class FactoryFactory<T>
        {
            public Func<T> Create(SimpleContainer container)
            {
                return () => (T)container.GetInstance(typeof(T), null);
            }
        }
    }

Here's what the bootstrapper would look like:

    using System;
    using System.Collections.Generic;

    public class YourBootstrapper : Bootsrapper
    {
        SimpleContainer container;

        protected override void Configure()
        {
            container = new SimpleContainer();
            //TODO: add your own registrations or discovery mechanism here
        }

        protected override object GetInstance(Type service, string key)
        {
            return container.GetInstance(service, key);
        }

        protected override IEnumerable GetAllInstances(Type service)
        {
            return container.GetAllInstances(service);
        }
    }

 

 

Jul 29, 2010 at 8:07 PM
Edited Jul 29, 2010 at 8:09 PM
I may have introduced some confusion with my advice... Using a DI container is definitely a better option; taking care of dependencies without it is real a pain.
What I intended to point out was the two opposite solution to the problem (is always better to make a conscious choice):
- delegating all the instantiation and dependency management work to a container, which obviously can handle it at the best; this is the option I usually choose and recommend;
- keeping things extremely simple using no external assembly nor addictional infrastructure (after all, Caliburn.Micro *can* work without a container out of the box): this may be a viable option (if the application is *really* simple) but you need to handle singleton lifetime of component and handle dependencies between services manually.

My advice about caching the instance was, indeed, a (rudimentary) tecnique to mimic singleton lifestyle.
It, however, should have been applied only to components which are intended to live for the *entire* application session.
But, again, when tweaking gets complex, is better to use a DI container.