如何多次导出一个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解决方案的问题。
你说:
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时在幕后遵循相同的过程:
. net运行时找到第一个弱类型插件程序集并从该位置加载它,然后MEF进行导出处理。
然后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进行实例化。