繁体   English   中英

如何从未由MEF容器实例化的对象导出部件

[英]How to export parts from an object not instantiated by the MEF container

介绍

SessionModel是一个提供多种服务的服务定位器(我将来会详细说明我的系统架构,但是现在我需要这样做)。

我将以下代码部分编辑为Short,Self Contained,Correct(Compilable),Example(SSCCE):

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var sessionModel = new SessionModel(3);

            // first case (see text down below):
            var compositionContainer = new CompositionContainer();

            // second case (see text down below):
            //var typeCatalog = new TypeCatalog(typeof (SessionModel));
            //var compositionContainer = new CompositionContainer(typeCatalog);

            compositionContainer.ComposeExportedValue(sessionModel);

            var someService = compositionContainer.GetExportedValue<ISomeService>();
            someService.DoSomething();
        }
    }

    public class SessionModel
    {
        private int AValue { get; set; }

        [Export]
        public ISomeService SomeService { get; private set; }

        public SessionModel(int aValue)
        {
            AValue = aValue;
            // of course, there is much more to do here in reality:
            SomeService = new SomeService();
        }
    }

    public interface ISomeService
    {
        void DoSomething();
    }

    public class SomeService : ISomeService
    {
        public void DoSomething()
        {
            Console.WriteLine("DoSomething called");
        }
    }
}

问题

我希望MEF在编写其他部分时考虑服务定位器导出的部分(即SomeService ),但遗憾的是这不起作用。

第一个案例

当我尝试获取ISomeService的导出值时, ISomeService出现System.ComponentModel.Composition.ImportCardinalityMismatchException告诉我没有带有此合约名称和所需类型标识的导出( ConsoleApplication1.ISomeService )。

第二个案例

如果我使用TypeCatalog创建CompositionContainer ,则异常略有不同。 它是一个System.ComponentModel.Composition.CompositionException告诉我MEF没有找到创建ConsoleApplication1.SessionModel (这是正确的,这也是我自己做的原因)。

附加信息

mefx对两种情况都说:

[Part] ConsoleApplication1.SessionModel from: DirectoryCatalog (Path=".")
  [Export] ConsoleApplication1.SessionModel.SomeService (ContractName="ConsoleApplication1.ISomeService")

[Part] ConsoleApplication1.SessionModel from: AssemblyCatalog (Assembly="ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")
  [Export] ConsoleApplication1.SessionModel.SomeService (ContractName="ConsoleApplication1.ISomeService")

我需要做什么? 这可能与MEF有关,还是我必须使用Unity或StructureMap,还是其他什么? 这可以实现ExportProvider吗?

好的,我就是这样做的:

我在SessionModel实现了自己的SessionModelExportProvider查找导出(参见下面的代码)。 SessionModelExport仅用于保存导出数据,而不是创建服务实例 - 返回SessionModel属性的值。

public class SessionModelExportProvider : ExportProvider
{
    private List<Export> Exports { get; set; }

    public SessionModelExportProvider(SessionModel sessionModel)
    {
        // get all the properties of the session model having an Export attribute
        var typeOfSessionModel = typeof (SessionModel);
        PropertyInfo[] properties = typeOfSessionModel.GetProperties();
        var propertiesHavingAnExportAttribute =
            from p in properties
            let exportAttributes = p.GetCustomAttributes(typeof (ExportAttribute), false)
            where exportAttributes.Length > 0
            select new
                       {
                           PropertyInfo = p,
                           ExportAttributes = exportAttributes
                       };

        // creating Export objects for each export
        var exports = new List<Export>();
        foreach (var propertyHavingAnExportAttribute in propertiesHavingAnExportAttribute)
        {
            var propertyInfo = propertyHavingAnExportAttribute.PropertyInfo;
            foreach (ExportAttribute exportAttribute in propertyHavingAnExportAttribute.ExportAttributes)
            {
                string contractName = exportAttribute.ContractName;
                if (string.IsNullOrEmpty(contractName))
                {
                    Type contractType = exportAttribute.ContractType ?? propertyInfo.PropertyType;
                    contractName = contractType.FullName;
                }

                var metadata = new Dictionary<string, object>
                                   {
                                       {CompositionConstants.ExportTypeIdentityMetadataName, contractName},
                                       {CompositionConstants.PartCreationPolicyMetadataName, CreationPolicy.Shared}
                                   };
                var exportDefinition = new ExportDefinition(contractName, metadata);
                var export = new SessionModelExport(sessionModel, propertyInfo, exportDefinition);
                exports.Add(export);
            }
        }

        Exports = exports;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition,
                                                          AtomicComposition atomicComposition)
    {
        return Exports.Where(e => definition.IsConstraintSatisfiedBy(e.Definition));
    }
}

public class SessionModelExport : Export
{
    private readonly SessionModel sessionModel;
    private readonly PropertyInfo propertyInfo;
    private readonly ExportDefinition definition;

    public SessionModelExport(SessionModel sessionModel, PropertyInfo propertyInfo, ExportDefinition definition)
    {
        this.sessionModel = sessionModel;
        this.propertyInfo = propertyInfo;
        this.definition = definition;
    }

    public override ExportDefinition Definition
    {
        get { return definition; }
    }

    protected override object GetExportedValueCore()
    {
        var value = propertyInfo.GetValue(sessionModel, null);
        return value;
    }
}

问题是SomeService是一个实例属性。 您的系统中可能有多个SessionModel对象,并且MEF无法知道哪个SessionModel正在返回应该与导入匹配的ISomeService实例。

相反,只需将SessionModel设为静态类,将SomeService设为静态属性即可。 或者,使SessionModel成为单例。 SomeService属性仍然是静态的,但会从SessionModel的唯一实例导出服务。

using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.ReflectionModel;
using System.Reflection;
using System.Linq;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {

            var catalogs = new AggregateCatalog();
            var catalog = new System.ComponentModel.Composition.Hosting.AssemblyCatalog(Assembly.GetExecutingAssembly());
            catalogs.Catalogs.Add(catalog);
            var sessionModel = new SessionModel(3);
            var container = new CompositionContainer(catalog); 
            ISomeService someService = container.GetExportedValueOrDefault<ISomeService>(sessionModel.cname);
            if (someService != null)
            {
                someService.DoSomething();
            }
        }
    }

    public class SessionModel
    {
        private int AValue { get; set; }

        //[Import("One",typeof(ISomeService))]
        //public ISomeService SomeService { get; private set; }

        public SessionModel(int aValue)
        {
            AValue = aValue;
            // of course, there is much more to do here in reality:
        }

        public string cname { get { return "One"; } }
    }

    public class SessionModel1
    {
        private int AValue { get; set; }

        //[Import("Two",typeof(ISomeService))]
        //public ISomeService SomeService { get; private set; }

        public SessionModel1(int aValue)
        {
            AValue = aValue;
        }
        public string cname { get { return "Two"; } }

    }

    public interface ISomeService
    {
        void DoSomething();
    }

    [Export("One",typeof(ISomeService))]
    public class SomeService : ISomeService
    {
        public SomeService()
        {
            Console.WriteLine("Some Service Called");
        }
        public void DoSomething()
        {
            Console.WriteLine("DoSomething called");
            Console.ReadKey();
        }
    }

    [Export("Two",typeof(ISomeService))]
    public class SomeService1 : ISomeService
    {
         public SomeService1()
        {
            Console.WriteLine("Some Service1 Called");
        }
        public void DoSomething()
        {
            Console.WriteLine("DoSomething called 1");
            Console.ReadKey();
        }
    }
}

第一种情况 :通过将sessionModel传递给ComposeExportedValue可以添加SessionModel类型的一部分而不是ISomeService 要使此案例有效,您需要将服务传递给ComposeExportedValue。

compositionContainer.ComposeExportedValue(sessionModel.SomeService);

第二种情况 :在这种情况下,您将零件的创建留给容器。 如果存在无参数构造函数或带有使用ImportingConstructorAttribute修饰的参数的构造函数,则容器可以创建新零件。 这很可能意味着您需要稍微改变您的设计。

就个人而言,我会选择第一种情况,但要尽量减少这种情况。 在MEF的所有正常(和建议)使用之后,让容器创建和处理零件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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