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

标签 c# .net mef

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

[编辑]

我的服务器有 CompositionContainer,需要与 6 个不同的目标(即交通灯 Controller )通信。对于每个目标,我想添加一个插件。插件逻辑是一样的,所以我只想维护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,每个针对不同的 Controller (读作:与不同的网络服务器通信)。 6 个 ControllerAdapter 中的逻辑是相等的。

我认为复制 ClassLibrary(例如复制到 1.dll、2.dll 等)并添加配置文件(1.dll.config 等)应该可以解决问题,但不是。

在组合时,我在容器中得到多个实例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 解决方案相比,这可能更多地与您如何使用程序集有关。

你说:

The logic in the 6 ControllerAdapters is equal.

那么是同一个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

Test result for two plugins in place, using the same plugin assembly copied over to two distinct plugin directories (probably your case):

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

You also get the exact same result if you give distinct names to those DLLs because effectively it is still the same assembly inside.

Now I add the third plugin, but this time it is a different plugin assembly:

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

The different assembly is of course found and identified correctly.

So this all boils down to how the .NET runtime handles assembly loading, which is a complex and strictly defined process and works differently for strongly and weakly named assemblies. I recommend this article for a good explanation of the process: Assembly Load Contexts Subtleties.

In this case, the same process is followed behind the scenes when using MEF:

  1. The .NET runtime finds the first weakly typed plugin assembly and loads it from that location and MEF does it's export processing.

  2. Then MEF tries to process the next plugin assembly it founds using the catalog, but the runtime sees the assembly with the same metadata already loaded. So it uses the already loaded one to look for exports and ends up instantiating the same type again. It does not touch the second DLL at all.

There is no way the same assembly can be loaded more than once by the runtime. Which makes perfect sense when you think of it. Assembly is just bunch of types with their metadata and once loaded the types are available, no need to load them again.

This may not be completely correct, but I hope it helps to explain where the problem lies and it should be clear that duplicating DLLs for this purpose is useless.

Now regarding what you want to achieve. It seems that all you need is just to get multiple instances of the SAME adapter plugin to use them for different purposes, which has nothing to do with multiplying DLLs.

To get multiple adapter instances you can define multiple imports with RequiredCreationPolicy set to CreationPolicy.NonShared in your server which MEF will accordingly instantiate for you:

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 实例化。

关于c# - 如何根据 app.config 多次导出一个 MEF 插件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15131014/

相关文章:

c# - 按文本而不是值对 Datagridview 的列进行排序

C# - 从 ZIP 中提取特定目录,保留文件夹结构

c# - .Net 4 图表控件问题

c# - 在 C# 中设置媒体元素源

c# - 具有整数值的 IFormatProvider

c# toolstrip 不在 menustrip 下停靠

c# - .NET 正则表达式 isMatch 与匹配问题

c# - 如何在运行时引用mvc中的程序集

c# - 从仅给定 Type 实例的 MEF 容器中获取导出

module - 如何使用 MEF 管理相互依赖的模块?