【问题标题】:How to export one MEF plugin multiple times, depending on app.config?如何根据 app.config 多次导出一个 MEF 插件?
【发布时间】:2013-02-28 08:41:56
【问题描述】:

我正在构建一个简单的 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);
    }
}

【问题讨论】:

  • 不清楚你想要什么。从您的问题正文中,您说您在容器中获得了多个版本,但您不知道如何进一步。那么,您需要控制从 app.config 导出的唯一问题是什么?如果您对自己的需求非常明确,那么有人会更有可能帮助您。
  • 试图明确我的目标。
  • 谢谢,这是有道理的。您应该能够使用界面上的InheritedExport 属性。这样您就可以从类中删除 Export 属性。然后,当您使用ImportMany 时,它将带回实现IFASTAdapter 的每个类。 blogs.geniuscode.net/JeremiahRedekop/?p=235
  • 谢谢,去试试!
  • 很奇怪,这不起作用。同样在这种情况下,我得到了对同一个程序集的引用。

标签: c# .net mef


【解决方案1】:

与使用 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 进行实例化。

【讨论】:

  • 好答案。这是要走的路,使用 PartCreationPolicy NonShared 并在需要时导入 IFASTAdapter 实例。
  • 太好了,这让我对程序集加载和 MEF 的工作方式有了很多了解。感谢您的回答!我现在看到我创建六个单独的程序集的方法是不必要的,它们都实现了相同的接口和基类。对于当前版本,我保留它现在的样子。但我肯定会尝试使用 CreationPolicy.NonShared 的方法。我很好奇如何使用 app.config 实例化各种实例,你能给我更多的信息吗?
  • @Cornelis 抱歉回复晚了,上周我很忙。关于 app.config 的使用,您是希望能够通过配置文件自己配置导入(即应该导入什么)还是只为特定插件实例配置参数,按照我描述的方式导入?这是两个完全不同的要求。此外,MEF 不能通过开箱即用的配置文件进行配置,它仅带有属性编程模型。
  • @ Famousgarkin 我想我现在主要关心的是如何有效地维护配置参数。我的项目包含很多程序集,实际上它们都加载在一个主程序中,即 Windows 服务。我注意到我必须将配置设置放在高级 app.config 中,否则它们似乎找不到。我已经找到了关于这个问题的stackoverflow.com/questions/89245/…
  • @ Famousgarkin 在我当前的设置中,使用基于基本适配器类和接口的插件,不需要 imo 进行基于配置的插件加载。但是,当我可能切换到您建议的 CreationPolicy 时,我看不到如何区分插件程序集(已复制,但具有不同的配置设置)。您在编写类中命名了 FirstAdapter 和 SecondAdapter,但是如何在插件中指定这些类?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
  • 2011-03-27
  • 2013-08-24
  • 1970-01-01
相关资源
最近更新 更多