Search code examples
c#mefcaliburn.micro

MEF2 & Caliburn.Micro - IWindowManager and IEventAggregator are not importing to ShellViewModel


I cannot get MEF2 to import a WindowManager or EventAggregator to my ShellViewModel. All my own Classes seem to work fine.

I've setting up my project to use MEF2 (System.ComponentModel.Composition and children). I followed the Customizing The Bootstrapper document to start as well as Tim Corey's From Zero to Proficient with MEF, and realized that these are MEF1. I did some reading, MEF 2 Preview Beginners Guide and Managed Extensibility Framework Improvements in .NET 4.5, replaced CompositionBatch with RegistrationBuilder, and got rid of the suggested Class and Property Attributes, to favour RegistrationBuilder's Fluid API for configuring imports and exports.

I get a NullReferenceException in the OnActivate override in the ShellViewModel, when it attempts to use _eventAggregator. The imports were never made.

If I run this by commenting out the OnActivate() and OnDeactivate(), it launches and displays a blank window, so it is loading the shell properly. It just isn't importing any dependencies.

Here is the simplest Bootstrapper and ViewModel to show the problem.

Bootstrapper.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Caliburn.Micro;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.Reflection;
using MEF2Test.ViewModels;

namespace MEF2Test
{
    public class MefBootstrapper : BootstrapperBase
    {
        private CompositionContainer _container;

        public MefBootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            RegistrationBuilder cmBuilder = new RegistrationBuilder();
            RegistrationBuilder cmpBuilder = new RegistrationBuilder();
            RegistrationBuilder vmBuilder = new RegistrationBuilder();

            cmBuilder.ForTypesDerivedFrom<IEventAggregator>().Export<IEventAggregator>();
            cmpBuilder.ForTypesDerivedFrom<IWindowManager>().Export<IWindowManager>();
            vmBuilder.ForTypesDerivedFrom<IShell>().Export<IShell>();

            // These added based on a reference I read, to use ImportProperty
            vmBuilder.ForTypesDerivedFrom<IShell>().ImportProperty<IEventAggregator>(x => x.EventAggregator);
            vmBuilder.ForTypesDerivedFrom<IShell>().ImportProperty<IWindowManager>(x => x.WindowManager);

            AggregateCatalog catalog = new AggregateCatalog();
            AssemblyCatalog cmAssembly = new AssemblyCatalog(typeof(IEventAggregator).GetTypeInfo().Assembly, cmBuilder);
            AssemblyCatalog cmpAssembly = new AssemblyCatalog(typeof(IWindowManager).GetTypeInfo().Assembly, cmpBuilder);
            AssemblyCatalog vmAssembly = new AssemblyCatalog(typeof(MefBootstrapper).GetTypeInfo().Assembly, vmBuilder);

            catalog.Catalogs.Add(cmAssembly);
            catalog.Catalogs.Add(cmpAssembly);
            catalog.Catalogs.Add(vmAssembly);

            _container = new CompositionContainer(catalog, CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe);
        }

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return new[] {
                typeof(IWindowManager).GetTypeInfo().Assembly,
                typeof(IEventAggregator).GetTypeInfo().Assembly,
                typeof(MefBootstrapper).GetTypeInfo().Assembly
            };
        }

        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.GetExports<object>(AttributedModelServices.GetContractName(serviceType));
        }

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

        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<IShell>();
        }
    }
}

ShellViewModel.cs

using Caliburn.Micro;

namespace MEF2Test.ViewModels
{
    public class ShellViewModel : Screen, IShell
    {
        private IWindowManager _windowManager;
        private IEventAggregator _eventAggregator;

        public ShellViewModel()
        {
        }

        protected override void OnActivate()
        {
            base.OnActivate();
            _eventAggregator.Subscribe(this);
        }

        protected override void OnDeactivate(bool close)
        {
            base.OnDeactivate(close);
            _eventAggregator.Unsubscribe(this);
        }

        // These 2 Properties added based on a reference I read, to use ImportProperty
        public IEventAggregator EventAggregator
        {
            get { return _eventAggregator; }
            set { _eventAggregator = value; }
        }

        public IWindowManager WindowManager
        {
            get { return _windowManager; }
            set { _windowManager = value; }
        }

    }

    public interface IShell
    {
        // These added based on a reference I read, to use ImportProperty
        IEventAggregator EventAggregator { get; set; }
        IWindowManager WindowManager { get; set; }
    }
}

Solution

  • I figured this out. For some reason, I must use:

    ForType<ConcreteType>.ImportProperty<Interface>(x => x.PublicPropertyToSetInterface);
    

    when registering the Imports. This has gotten my simple example working, so that answers my question. My actual project still suffers from a

    System.InvalidCastException: 'Unable to cast object of type 'System.Lazy`1[System.Object]' to type 'Caliburn.Micro.IWindowManager'.'
    

    error, but that's probably something I've missed or misrepresented in my registrations.