MEF problem with manually created Screen

Apr 1, 2011 at 11:37 AM

Hey guys,

I have a simple WPF wizard with a few screens.  I am using the standard MEF bootstrapper, with one small change - I am using the name of my first ViewModel instead of an IShell.

public class BootStrapper : Bootstrapper<MessageTypeViewModel>

The WindowManager class is created by MEF, and I am using this to handle window creation and navigation.  The import of this seems to work for the first window displayed (MessageTypeView), but all other imports fail for the subsequent windows I create manually with the following code:

[Import] 
private IWindowManager _windowManager;

_windowManager.ShowWindow(new ClassViewModel(_messageType));

I have managed to get this working using IOC.Get(), but understand that this is bad practice and should be avoided if possible.

Any ideas why the views/viewmodels I create manually don't get the dependencies satisfied by MEF?


 

Coordinator
Apr 1, 2011 at 2:14 PM

Can you post the code of your bootstrapper?

Apr 1, 2011 at 10:12 PM

Hey Rob.  Here is my bootstrapper:

public class BootStrapper : Bootstrapper<MessageTypeViewModel>
    {
        static BootStrapper()
        {
            LogManager.GetLog = type => new DebugLogger(type);
        }

        private CompositionContainer _container;

        protected override void Configure()
        {
            var catelog = new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
            _container = CompositionHost.Initialize(catelog);

            var batch = new CompositionBatch();
            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(_container);

            _container.Compose(batch);
        }

        //protected override IEnumerable<Assembly> SelectAssemblies()
        //{
        //    return new[] {
        //        Assembly.GetExecutingAssembly()
        //    };
        //}

        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.Count() > 0)
                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);
        }

Coordinator
Apr 1, 2011 at 10:22 PM
Edited Apr 1, 2011 at 10:22 PM

Ah. I understand your problem. It's ClassViewModel that isn't getting its dependencies satisfied. That makes sense. The only way for that to happen is if it is created by MEF or you call IoC.BuildUp on the existing instance. You should have a look into Export Factories http://mef.codeplex.com/wikipage?title=PartCreator as an alternative to creating the instances by hand or pulling from the container. It's available in the SL version of MEF and the CTP of MEF v2 for desktop.

Apr 1, 2011 at 10:34 PM

Cool, thanks Rob Export Factories look great.  I suppose I could solve the problem by creating the ViewModels and adding them directly to my container, and then import them when needed.  Could you see any negative effects of this?  Could get a bit messy if you start to have lots of views...

Thanks for your help, and on another note I'm loving Caliburn.Micro.  I've just moved to it from previously using MVVMLight.  I got to remove SOOO much plumbing code and what I've got left with now is heaps cleaner.  Keep up the good work.