4

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

[编辑]

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

我尝试将插件放在子目录中,然后递归地通过这些目录将插件添加到目录中。然而,这不起作用。在子目录中找到的第二个插件将被导入,但这个插件是针对第一个插件的。当循环通过容器 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 个 ControllerAdapter,每个都针对不同的控制器(阅读:与不同的网络服务器通信)。6 个 ControllerAdapter 中的逻辑是相等的。

我认为复制 ClassLibrary(例如到 1.dll、2.dll 等)并添加配置文件(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);
    }
}
4

1 回答 1

5

与使用 MEF 解决方案相比,使用程序集的方式可能更成问题。

你说:

6 个 ControllerAdapter 中的逻辑是相等的。

那么是同一个 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();
}

一个插件的测试结果:

找到的插件: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 是没有用的。

现在关于你想要达到的目标。您似乎只需要获取 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.config 进行实例化。

于 2013-03-14T09:47:34.777 回答