如何根据app.config多次导出一个MEF插件?

4
我正在构建一个简单的MEF应用。我想实现的是构建一个插件,可以在同一组合应用程序中多次注册。插件的注册应该取决于插件配置文件中的设置,但我无法做到这一点。
[编辑]
我的服务器拥有CompositionContainer,需要与6个不同的目标(即红绿灯控制器)进行通信。对于每个目标,我都想添加一个插件。插件逻辑相同,因此我只想维护一个插件。每个目标都有自己的Web地址以进行通信(以及其他一些配置项),我希望这些内容在(单独的)配置文件中。
我尝试将插件放置在子目录中,并递归浏览这些目录以将插件添加到目录中。然而,这并没有起作用。在子目录中找到的第二个插件将被导入,但是这个插件会定位到第一个插件。当循环遍历容器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,每个Adapter针对一个不同的控制器(即与不同的Web服务器通信)。 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);
    }
}

不是很清楚你想要什么。从你的问题描述中,你说你在容器中得到了多个版本,但是不知道怎样进一步操作。那么,唯一的问题是你需要控制从app.config导出吗?如果你非常明确地说明你需要什么,有人会更愿意帮助你。 - ChrisO
试图清楚地表达我的目标。 - Cornelis
谢谢,那很有道理。你应该能够在你的接口上使用InheritedExport属性。这样你就可以从类中删除Export属性。当你使用ImportMany时,它会带回实现IFASTAdapter的每个类。http://blogs.geniuscode.net/JeremiahRedekop/?p=235 - ChrisO
谢谢,我会尝试的! - Cornelis
很奇怪,这个不起作用。在这种情况下,我也得到了对同一程序集的引用。 - Cornelis
显示剩余3条评论
1个回答

5
这可能更多是与您使用程序集的方式有关,而不是MEF解决方案本身的问题。
您说:
“6个ControllerAdapters中的逻辑相同。”
那么这是相同的DLL文件只是复制到不同的插件目录下吗?如果是,那么这就是问题所在。
我模拟了您的方法并运行了一些测试来证明我的想法。代码与您的代码实际上是相同的,并从服务器的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();
}

测试一个插件的结果如下:

找到的插件:1
AdapterPlugin.ControllerAdapter AdapterPlugin,版本=1.0.0.0,文化=中性,PublicKeyToken=null file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL

测试两个插件放置在同一位置,使用相同的插件程序集复制到两个不同的插件目录中(可能是您的情况)的结果:

找到的插件:2
AdapterPlugin.ControllerAdapter AdapterPlugin,版本=1.0.0.0,文化=中性,PublicKeyToken=null file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
AdapterPlugin.ControllerAdapter AdapterPlugin,版本=1.0.0.0,文化=中性,PublicKeyToken=null file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL

如果为这些DLL提供不同的名称,则会得到完全相同的结果,因为实际上它们仍然是内部相同的程序集。

现在我添加第三个插件,但这次是使用不同的插件程序集:

找到的插件:3
AdapterPlugin.ControllerAdapter AdapterPlugin,版本=1.0.0.0,文化=中性,PublicKeyToken=null file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
AdapterPlugin.ControllerAdapter AdapterPlugin,版本=1.0.0.0,文化=中性,PublicKeyToken=null file:///C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL
AdapterPlugin2.ControllerAdapter AdapterPlugin2,版本=1.0.0.0,文化=中性,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是没有意义的。
现在关于你想要实现的内容。看起来你只需要获取相同适配器插件的多个实例以将它们用于不同的目的,这与复制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.config配置和实例化什么以及如何实现。

好的答案。这是正确的方法,使用PartCreationPolicy NonShared并在需要时导入IFASTAdapter实例。 - Marc
@Cornelis 对于我上周的回复迟了,很抱歉。关于 app.config 的使用,您是想通过配置文件自己配置导入内容(即应该导入什么),还是只想配置特定插件实例的参数,就像我描述的那样导入?这是两个非常不同的需求。此外,MEF 默认情况下无法通过配置文件进行配置,它只提供了带属性的编程模型。 - famousgarkin
@famousgarkin 我目前最关心的问题是如何有效地维护配置参数。我的项目包含许多程序集,实际上都加载在一个主程序中,该程序是一个Windows服务。我已经注意到,我必须将配置设置放在高级别的app.config文件中,否则它们似乎无法被找到。我已经找到了https://dev59.com/RXVD5IYBdhLWcg3wGXlI,这是关于这个问题的。 - Cornelis
@famousgarkin 在我的当前设置中,插件基于一个基本的AdapterClass和一个接口,我认为没有必要使用基于配置的插件加载。然而,当我可能切换到您建议的CreationPolicy时,我不知道如何区分插件程序集(已复制,但具有不同的配置设置)。您在组合类中命名了FirstAdapter和SecondAdapter,但我如何在插件中指定这些类呢? - Cornelis
@famousgarkin 谢谢,我一会儿会尝试一些东西。现在得先处理另一个项目 :) - Cornelis
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接