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

Cor*_*lis 4 .net c# mef

我正在构建一个简单的 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);
}
Run Code Online (Sandbox Code Playgroud)

我不知道我是否也可以使用 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);
}
Run Code Online (Sandbox Code Playgroud)

插件

namespace FAST.DevIS.ControllerAdapter
{
   [Export (typeof(IFASTAdapter))]
   public class ControllerAdapter : IFASTAdapter
   {
       ...
   }
}
Run Code Online (Sandbox Code Playgroud)

界面

namespace FAST.Common.FastAdapter
{
    public interface IFASTAdapter
    {
        /// Parse plan parameters
        /// 
        //Activator
        bool ParsePlan(PlansContainer plan);
        bool ActivatePlan();
        void Configure(string config);
    }
}
Run Code Online (Sandbox Code Playgroud)

fam*_*kin 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();
}
Run Code Online (Sandbox Code Playgroud)

一个插件的测试结果:

找到的插件: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

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

找到的插件: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 指定不同的名称,也会得到完全相同的结果,因为实际上它内部仍然是相同的程序集。

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

找到的插件: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,版本=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 无关。

要获得多个适配器实例,您可以在服务器中使用RequiredCreationPolicyset to定义多个导入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);
    }
}
Run Code Online (Sandbox Code Playgroud)

相应的 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));
}
Run Code Online (Sandbox Code Playgroud)

如果所有这些都在某种程度上对您有所帮助,我们还可以看看您想如何配置什么以及如何使用 app.config 进行实例化。