2013-02-28 51 views
4

我正在构建一个简单的MEF应用程序。我想要实现的是构建一个插件,它可以在同一个组合应用程序中多次注册。插件的注册应该取决于插件配置文件的设置,但我无法做到这一点。如何根据app.config多次导出一个MEF插件?

[编辑]

我的服务器,它具有CompositionContainer中,需要与6个不同的靶标(即,交通灯控制器)进行通信。对于每个目标,我想添加一个插件。插件的逻辑是一样的,所以我只想维护一个插件。每个目标都有自己的Web地址进行通信(以及其他一些配置项),我希望这些目标位于(单独)configfiles中。

我试过的是将插件放在子目录中,并递归地通过这些目录来添加目录中的插件。然而,这不起作用。在子目录中找到的第二个插件将被导入,但是这个插件是针对第一个插件的。在循环容器FASTAdapters时,所有部分看起来都等于第一个。

private void Compose() 
{ 
    var catalog = new AggregateCatalog(); 
    string sDir = AppSettingsUtil.GetString("FASTAdaptersLocation", @"./Plugins"); 
    foreach (string d in Directory.GetDirectories(sDir)) 
    { 
     catalog.Catalogs.Add(new DirectoryCatalog(d)); 
    } 
    var container = new CompositionContainer(catalog); 
    container.ComposeParts(this); 
} 

我不知道我是否也可以使用ExportMetadata属性。看来ExportMetadata属性必须是硬编码的,但我希望如果可能的话,从配置文件中读取属性。

[/编辑]

我的目标是具有6个ControllerAdapters,每个针对不同的控制器(读:与不同的网络服务器通信)。 6 ControllerAdapters中的逻辑是相同的。我认为复制ClassLibrary(例如到1.dll,2.dll等),并添加configfiles(1.dll.config等)应该做的伎俩,但不。

在撰写时,我在容器中获得多个typeof(FAST.DevIS.ControllerAdapter)实例,但我不知道如何获得更多。

我需要在导出中用MetaData做些什么吗?

的进口服务器

[ImportMany] 
public IEnumerable<IFASTAdapter> FASTAdapters { get; set; } 

private void Compose() 
{ 
    var catalog = new AggregateCatalog(); 
    catalog.Catalogs.Add(new DirectoryCatalog(AppSettingsUtil.GetString("FASTAdaptersLocation", Path.GetDirectoryName(Assembly.GetAssembly(typeof(ControllerServer)).Location)))); 
    var container = new CompositionContainer(catalog); 
    container.ComposeParts(this); 
} 

插件

namespace FAST.DevIS.ControllerAdapter 
{ 
    [Export (typeof(IFASTAdapter))] 
    public class ControllerAdapter : IFASTAdapter 
    { 
     ... 
    } 
} 

namespace FAST.Common.FastAdapter 
{ 
    public interface IFASTAdapter 
    { 
     /// Parse plan parameters 
     /// 
     //Activator 
     bool ParsePlan(PlansContainer plan); 
     bool ActivatePlan(); 
     void Configure(string config); 
    } 
} 
+0

这是不是很清楚你想要什么。从你的问题主体,你说你在容器中有多个版本,但你不知道如何进一步。那么,是否需要通过app.config控制导出才是唯一的问题?如果你非常清楚你需要什么,有人会更有可能帮助你。 – ChrisO 2013-02-28 14:17:04

+0

试图清楚我的目标是什么。 – Cornelis 2013-02-28 14:40:11

+0

谢谢,这是有道理的。你应该能够在你的界面上使用'InheritedExport'属性。这样你可以从类中删除'Export'属性。当你使用'ImportMany'时,它将带回每个实现'IFASTAdapter'的类。 http://blogs.geniuscode.net/JeremiahRedekop/?p=235 – ChrisO 2013-02-28 16:29:57

回答

4

这可能是更多的与您如何使用组件不是与MEF解决的问题的接口。

你说:

逻辑在6个ControllerAdapters相等。

那么,它是相同的DLL只是复制6次到不同的插件目录?如果是,那么这就是问题所在。

我模拟你的方法并运行一些测试来证明我的想法。代码实际上与您的代码相同,并从服务器bin/plugin目录的子目录中读取插件。

简单测试使用NUnit行使服务器类库:在地方,一个插件

[Test] 
public void Compose() 
{ 
    var server = new Server(); 
    server.Compose(); 
    Console.WriteLine("Plugins found: " + server.FASTAdapters.Count()); 
    Console.WriteLine(); 
    foreach (var adapter in server.FASTAdapters) 
    { 
     Console.WriteLine(adapter.GetType()); 
     Console.WriteLine(adapter.GetType().Assembly.FullName); 
     Console.WriteLine(adapter.GetType().Assembly.CodeBase); 
     Console.WriteLine(); 
    } 
    Assert.Pass(); 
} 

测试结果:在地方两个插件

 
Plugins found: 1 

AdapterPlugin.ControllerAdapter 
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL 

测试结果,使用相同的插件组件复制到两个不同的插件目录(可能是你的情况):

 
Plugins found: 2 

AdapterPlugin.ControllerAdapter 
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL 

AdapterPlugin.ControllerAdapter 
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL 

如果你给这些DLL赋予不同的名字,你也会得到完全相同的结果,因为它们仍然是相同的程序集。

现在我添加第三个插件,这一次却是一个不同的插件组件:

 
Plugins found: 3 

AdapterPlugin.ControllerAdapter 
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL 

AdapterPlugin.ControllerAdapter 
AdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL 

AdapterPlugin2.ControllerAdapter 
AdapterPlugin2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null 
file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER3/ADAPTERPLUGIN2.DLL 

不同的组件过程中发现并确定正确的。

所以这一切都归结为.NET运行时如何处理程序集加载,这是一个复杂和严格定义的过程,对强命名和弱命名程序集的工作方式不同。我推荐这篇文章对这个过程有一个很好的解释:Assembly Load Contexts Subtleties

在这种情况下,使用MEF时相同的过程之后幕后:

  1. .NET运行库发现从该位置的第一弱类型的插件组件,并将其加载和MEF它的出口加工。

  2. 然后MEF尝试使用该目录处理它创建的下一个插件程序集,但运行时发现程序集中已经加载了相同的元数据。所以它使用已经加载的一个来查找导出并最终再次实例化相同的类型。它根本不碰第二个DLL。

运行时不能一次加载同一个程序集。当你想到它时,这是非常有意义的。装配只是一堆类型与他们的元数据,一旦加载类型可用,无需再次加载它们。

这可能不完全正确,但我希望它有助于解释问题出在哪里,而且应该清楚为此目的复制DLL是无用的。

现在关于你想达到什么。看起来你需要的只是获取SAME适配器插件的多个实例,将它们用于不同的目的,这与乘法DLL无关。

为了让多个适配器实例,你可以定义你的服务器RequiredCreationPolicy集多种进口CreationPolicy.NonShared这MEF将相应实例为您:

public class Server 
{ 
    [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] 
    public IFASTAdapter FirstAdapter { get; set; } 

    [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] 
    public IFASTAdapter SecondAdapter { get; set; } 

    // Other adapters ... 

    public void Compose() 
    { 
     var catalog = new AggregateCatalog(); 
     var pluginsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins"); 
     foreach (string d in Directory.GetDirectories(pluginsDir)) 
     { 
      catalog.Catalogs.Add(new DirectoryCatalog(d)); 
     } 
     var container = new CompositionContainer(catalog); 
     container.ComposeParts(this); 
    } 
} 

对应NUnit测试,以检查适配器实例,他们是不同的实例:

[Test] 
public void Compose_MultipleAdapters_NonShared() 
{ 
    var server = new Server(); 
    server.Compose(); 
    Assert.That(server.FirstAdapter, Is.Not.Null); 
    Assert.That(server.SecondAdapter, Is.Not.Null); 
    Assert.That(server.FirstAdapter, Is.Not.SameAs(server.SecondAdapter)); 
} 

如果这一切都将帮助你在一定程度上,我们也可以看你想如何配置什么以及如何使用app.con实例图。

+0

很好的答案。这是要走的路,使用PartCreationPolicy NonShared并根据需要导入IFASTAdapter的实例。 – Marc 2013-03-14 10:27:46

+0

好极了,这给了我很多关于装配加载和MEF如何工作的见解。感谢您的回答!我现在看到,我的方法创建六个独立的程序集,都实现相同的接口和基类,没有必要。对于目前的版本,我保持现在的样子。但是我一定会用CreationPolicy.NonShared尝试你的方法。我很好奇如何使用app.config实例化各种实例,你能给我更多的信息吗? – Cornelis 2013-03-14 13:57:01

+0

@Cornelis对不起,最近一周我很忙。关于app.config的用法,是否希望能够通过配置文件(即应该导入的内容)自己配置导入,还是只想为特定的插件实例配置参数,按照我描述的方式导入?这是两个完全不同的要求。此外,MEF不能由开箱即用的配置文件进行配置,它仅附带归属编程模型。 – famousgarkin 2013-03-26 13:29:36