Add ViewModels to IoC container -> null reference exception

Dec 20, 2010 at 7:13 PM

In my bootstrapper I have this piece of code:

 

        protected override void Configure()
        {
            container = new CompositionContainer(new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                ));

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue<ViewModel1>(new ViewModel1());
	    batch.AddExportedValue(container);
            container.Compose(batch); }

 

 

In the constructor of ViewModel1 I have:

        public ViewModel1()
        {
            var aggregator = IoC.Get<IEventAggregator>();
        }

This throws an exception because the GetInstance delegate in the IoC class is null...
So im trying to load my ViewModels into the IoC container...

Dec 20, 2010 at 9:57 PM

Are you already using the MEF bootstrapper implementation?

Dec 20, 2010 at 10:35 PM

Once you got the bootstrapper fixed as BladeWise correctly suggested, you might also consider refactoring your code to avoid direct call to IoC.Get (aka ServiceLocator) as much as possible:

[ImportingConstructor]
public ViewModel1(IEventAggregator aggregator) {

	this.aggregator = aggregator;
}

This way you can leverage constructor dependency injection, with the advantage of "declaring" required services explicitly in a single point (the constructor), thus simplifying manteinance and unit testing.

 

Dec 21, 2010 at 6:41 AM

I'm already using the MEF bootstrapper implementation, yes.

Can all my ViewModels use Construtor Injection? Or whats best practice here?

Dec 21, 2010 at 8:19 AM

Generally speaking, using the constructor injection is widely considered a better practice to acquire service dependencies.
ServiceLocator pattern has its fields of application, expecially in infrastructure code, but usually make harder for readers of code to identify the required dependencies of a class.

That being said, back to your issue.
IoC.GetInstance delegate (along with .GetAllInstances and .BuildUp) is initialized in the Bootstrapper constructor:

        //from CM source code
        public Bootstrapper()
        {
            ...
            IoC.GetInstance = GetInstance;
            IoC.GetAllInstances = GetAllInstances;
            IoC.BuildUp = BuildUp;
        }

If, at some point in time, one of the IoC delegate is null, you should have reset it somewhere, or the Bootstrapper may not be running at all.
Could you confirm that none of the previous hypothesis occured?

Dec 21, 2010 at 8:27 AM

My Bootstrapper looks like the MEF bootstrapper in the documentation.

public class Bootstrapper : Bootstrapper<IShell>
    {
        private CompositionContainer container;

        protected override void Configure()
        {
            container = new CompositionContainer(new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                ));

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue<ViewModel1>(new ViewModel1());
            batch.AddExportedValue(container);

            container.Compose(batch);
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if (exports.Any())
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);

        }
    }

 

The bootstrapper is running and I dont override the GetInstance delegate...

Dec 21, 2010 at 9:34 AM

Could you post here the exact exception you are having?

Moreover, you could do a Find All Rerences for the IoC.GetInstance delegate and verify if it is set somewhere in your code.

Dec 21, 2010 at 9:41 AM

It's just a regular null reference exception.

If I remove this line

batch.AddExportedValue<ViewModel1>(new ViewModel1());

And then use the event aggregator on a later moment, no problem.

So it looks like the GetInstance delegate is not set when I try to call it...?



 

 

Dec 21, 2010 at 10:27 AM
Edited Dec 21, 2010 at 10:28 AM

Ah, I see! The Configure method is called before the IoC delegates are set. This means that at the time ViewModel1 is created, the IoC is not yet initialized.

To overcome the problem, you can follow one of the following approaches:

  •  Pass the instance of the EventAggregator to the ViewModel1 constructor (preferrable, in my opnion, so that the dependency is explicit)

    public class Bootstrapper : Bootstrapper<IShell>
    {
        private CompositionContainer container;

        protected override void Configure()
        {
            container = new CompositionContainer(new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));

            var batch = new CompositionBatch();
            var manager = new EventAggregator();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(manager);
            batch.AddExportedValue<ViewModel1>(new ViewModel1(manager));
            batch.AddExportedValue(container);

            container.Compose(batch);
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if (exports.Any())
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);

        }
    }

 

  •  Put your exports on a function called by the constructor, to be sure that the initialization part is completed before calling your code
    public class Bootstrapper : Bootstrapper<IShell>
    {
        private CompositionContainer container;

        public Bootstrapper()
        {
              ConfigureExports();
        }

        protected override void Configure()
        {
            container = new CompositionContainer(new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                ));
        }

        private void ConfigureExports()
        {
            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue<ViewModel1>(new ViewModel1());
            batch.AddExportedValue(container);

            container.Compose(batch);
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if (exports.Any())
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);

        }
    }


 

Both implementations should be enough to fix your problem.

Dec 21, 2010 at 10:30 AM
Edited Dec 21, 2010 at 10:32 AM

Evil is in the details :-) 

(facepalm moment)

Dec 21, 2010 at 10:45 AM

Thanks!

Ill think im going for the second option.

Cheers!