使用PCL MEF2和Caliburn.Micro

时间:2015-04-08 19:19:00

标签: c# .net mef caliburn.micro portable-class-library

我不确定如何连接Caliburn.Micro以使用PCL版本的MEF2。我已经看过MefBootstrapper example,但是它使用了许多无法使用的课程,而且我无法转换为新的API。

这是我到目前为止所拥有的:

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

但是,CompositionHost似乎没有任何导出,我不知道如何向它添加对象(WindowManager和EventAggregator)。

2 个答案:

答案 0 :(得分:1)

稍微玩了一下,这就是我想出来的东西,似乎有效:

[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>();
    }
}

答案 1 :(得分:0)

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.