简体   繁体   中英

Using PCL MEF2 with Caliburn.Micro

I'm not sure how to wire up Caliburn.Micro to use the PCL version of MEF2. I've seen the MefBootstrapper example , but that uses a lot of classes that aren't available and I'm having trouble converting to the new API.

Here's what I have so far:

using System;
using System.Collections.Generic;
using System.Composition;
using System.Composition.Hosting;
using System.Linq;
using Caliburn.Micro;

namespace Test
{
    public class Bootstrapper : BootstrapperBase
    {
        private CompositionHost _host;

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            var config = new ContainerConfiguration();
            config.WithAssemblies(AssemblySource.Instance);

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

            _host = config.CreateContainer();
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? serviceType.ToString() : key;
            var exports = _host.GetExports<object>(contract).ToArray();

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

        protected override void BuildUp(object instance)
        {
            _host.SatisfyImports(instance);
        }

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

However, the CompositionHost does not appear to have any exports, and I have no idea how to add objects (WindowManager and EventAggregator) to it.

After playing with it a bit, here's what I came up with and it seems to work:

[Export(typeof(IWindowManager))]
public class MyWindowManager : WindowManager
{
}

[Export(typeof(IEventAggregator))]
public class MyEventAggregator : EventAggregator
{
}

public interface IShell
{
}

public class AppBootstrapper : BootstrapperBase
{
    private CompositionHost _host;

    public AppBootstrapper()
    {
        Initialize();
    }

    protected override IEnumerable<Assembly> SelectAssemblies()
    {
        // TODO: Add additional assemblies here
        yield return typeof(AppBootstrapper).GetTypeInfo().Assembly;
    }

    protected override void Configure()
    {
        var config = new ContainerConfiguration();
        var assemblies = AssemblySource.Instance.Union(SelectAssemblies());
        config.WithAssemblies(assemblies);

        _host = config.CreateContainer();
    }

    protected override object GetInstance(Type serviceType, string key)
    {
        var exports = _host.GetExports(serviceType, key).ToArray();

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

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

    protected override IEnumerable<object> GetAllInstances(Type serviceType)
    {
        return _host.GetExports<object>(serviceType.ToString());
    }

    protected override void BuildUp(object instance)
    {
        _host.SatisfyImports(instance);
    }

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

mine is certainly a late reply, but maybe this is helpful to someone else struggling with the poor MEF2 docs, and surely it might help me too, if anyone comes up with better implementations, or finds any issues in this solution; so here it is.

For a less attribute-oriented approach (which is one of the essential MEF2 features), and to avoid the hack of wrapping the CM injectables into custom classes, you must configure the assemblies exports, as follows:

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

protected override void Configure()
{
    var config = new ContainerConfiguration();

    // note that the event aggregator is in the core CM assembly,
    // while the window manager in the platform-dependent CM assembly,
    // so that we need 2 conventions for 2 assemblies.
    ConventionBuilder cmBuilder = new ConventionBuilder();
    cmBuilder.ForType<EventAggregator>().Export<IEventAggregator>();

    ConventionBuilder cmpBuilder = new ConventionBuilder();
    cmpBuilder.ForType<WindowManager>().Export<IWindowManager>();

    ConventionBuilder appBuilder = new ConventionBuilder();
    appBuilder.ForTypesMatching(t =>
        t.Name.EndsWith("ViewModel", StringComparison.OrdinalIgnoreCase)).Export();
    appBuilder.ForType<MainViewModel>().Export<IShell>();

    config.WithAssembly(typeof(IEventAggregator).GetTypeInfo().Assembly, cmBuilder);
    config.WithAssembly(typeof(IWindowManager).GetTypeInfo().Assembly, cmpBuilder);
    config.WithAssembly(typeof(MefBootstrapper).GetTypeInfo().Assembly, appBuilder);

    _host = config.CreateContainer();
}

Essentially, you must pay attention to a couple of things:

  1. the CM objects are distributed into different assemblies, as some are shared among all the platforms, and others are more platform-specific. In this case, the event aggregator is found in the CM core assembly, while the window manager is in the Caliburn.Micro.Platform one.
  2. in MEF2 you can use conventions to automatically mark as exports the required objects. In my example, I'm marking as exports the EventAggregator as the implementation to be selected for the interface IEventAggregator , and similarly for the window manager; further, I'm exporting my main view model as the implementation for the IShell interface, and all the viewmodels by exporting all the classes from my app assembly whose name ends with ViewModel . This way, I do not require any ExportAttribute .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM