如何多次导出一个MEF插件,取决于app.config

本文关键字:插件 MEF 取决于 config app 一个 何多次 | 更新日期: 2023-09-27 18:01:27

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

[编辑]

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

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

我认为复制ClassLibrary(例如到1.dll, 2.dll等)和添加配置文件(1.dll。配置等)应该可以解决这个问题,但是没有。

组合时,我在容器中得到多个实例typeof(FAST.DevIS.ControllerAdapter),但我不知道如何进一步。

我需要在导出中处理元数据吗?

导入服务器

[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);
    }
}

如何多次导出一个MEF插件,取决于app.config

这可能是您如何使用程序集而不是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();
}

一个插件的测试结果:

<>之前找到的插件AdapterPlugin。ControllerAdapterAdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null文件://C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL之前

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

<>之前找到的插件AdapterPlugin。ControllerAdapterAdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null文件://C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLLAdapterPlugin。ControllerAdapterAdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null文件://C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLL之前

如果你给这些dll不同的名字,你也会得到完全相同的结果,因为实际上它仍然是同一个程序集。

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

<>之前找到的插件AdapterPlugin。ControllerAdapterAdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null文件://C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLLAdapterPlugin。ControllerAdapterAdapterPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null文件://C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER1/ADAPTERPLUGIN.DLLAdapterPlugin2。ControllerAdapterAdapterPlugin2,版本=1.0.0.0,文化=中性,PublicKeyToken=null文件://C:/USERS/GARKIN/DOCUMENTS/VISUAL STUDIO 2012/PROJECTS/MEFADAPTERS/ADAPTERSERVER/BIN/DEBUG/PLUGINS/ADAPTER3/ADAPTERPLUGIN2.DLL之前

当然可以找到并正确识别不同的程序集。

所以这一切都归结为。net运行时如何处理程序集加载,这是一个复杂且严格定义的过程,并且对于强命名和弱命名的程序集的工作方式不同。我推荐这篇文章,因为它很好地解释了这个过程:汇编加载上下文的微妙之处。

在本例中,使用MEF时在幕后遵循相同的过程:

  1. . net运行时找到第一个弱类型插件程序集并从该位置加载它,然后MEF进行导出处理。

  2. 然后MEF尝试处理它使用目录找到的下一个插件程序集,但是运行时看到该程序集已经加载了相同的元数据。因此,它使用已经加载的类型来查找导出,并最终再次实例化相同的类型。

运行时不可能多次加载同一个程序集。仔细想想,这是完全合理的。程序集只是一堆类型和它们的元数据,一旦加载,这些类型就可用了,不需要再加载它们。

这可能不完全正确,但我希望它有助于解释问题所在,并且应该清楚地表明,为此目的复制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进行实例化。