How to export one MEF plugin several times, depending on app.config?

I am creating a simple MEF application. I want to create one plugin that can be registered several times in one composite application. The registration of the plugin should depend on the settings from the plugin configuration file, but I can not do this.

[edit]

My server, which has a CompositionContainer, needs to interact with 6 different targets (e.g. Traffic Light controllers). For every purpose I want to add a plugin. The logic of the plugin is the same, so I want to support only 1 plugin. Each target has its own web address for communication (and some other configuration items), I want them in (separate) configuration files.

What I tried is to put the plugins in subdirectories and navigate through these directories to add the plugins to the directory. However, this does not work. The second plugin found in the subdirectors will be imported, but it targets the first plugin. When passing through a FASTAdapters container, all parts appear to be equal to the first.

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

I do not know if I can also use the ExportMetadata attribute. It seems that ExportMetadata attributes should be hardcoded, but I want the attribute to be read from the configuration file, if possible.

[/ edit]

My goal is to have 6 controller adapters, each of which is aimed at a different controller (read: communication with another web server). The logic in 6 ControllerAdapters is equal.

I thought that copying ClassLibrary (e.g. to 1.dll, 2.dll, etc.) and adding configuration files (1.dll.config, etc.) should do the trick, but no.

When compiling, I get several instances of typeof(FAST.DevIS.ControllerAdapter) in the container, but I do not know how to do this further.

Do I need to do something with MetaData in the export?

Importing server

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

Plugin

 namespace FAST.DevIS.ControllerAdapter { [Export (typeof(IFASTAdapter))] public class ControllerAdapter : IFASTAdapter { ... } } 

Interface

 namespace FAST.Common.FastAdapter { public interface IFASTAdapter { /// Parse plan parameters /// //Activator bool ParsePlan(PlansContainer plan); bool ActivatePlan(); void Configure(string config); } } 
+4
source share
1 answer

This may be more of a problem with how you use assemblies than with the MEF solution.

You speak:

The logic in 6 ControllerAdapters is equal.

Is this same DLL copied 6 times to different plugin directories? If so, then this is a problem.

I modeled your approach and conducted some tests to prove what I was thinking. The code is actually the same as yours and reads plugins from subdirectories of the bin / plugin server directory.

A simple test using NUnit to implement a server class library:

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

Test results for one plugin in place:

  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 collector that was copied to two different plugin directories (maybe 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 will also get the same result if you give different names for these DLLs, because in essence it is still the same assembly inside.

Now I am adding a third plugin, but this time it is a different assembly of plugins:

  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

A different assembly, of course, was found and identified correctly.

So, it all comes down to how the .NET runtime processes an assembly, which is a complex and well-defined process and works differently for strongly and weakly named assemblies. I recommend this article for a good explanation of the process: Assembly load routines .

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

  • The .NET runtime finds the first weakly connected plug-in assembly and loads it from this location, and MEF performs export processing.

  • Then, MEF tries to process the next assembly of the plugin that it found using the directory, but the runtime sees the assembly with the metadata already loaded. Thus, it uses the already loaded one to search for exports and finishes creating an instance of the same type again. This does not apply to the second DLL at all.

It is not possible for the same assembly to load more than once using the runtime. What makes sense when you think about it. An assembly is just a bunch of types with their metadata, and after loading the types are available, there is no need to load them again.

This may not be entirely correct, but I hope this helps explain where the problem is, and it should be clear that duplicating a DLL is useless for this purpose.

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

To get multiple adapter instances, you can define multiple imports with RequiredCreationPolicy installed on CreationPolicy.NonShared on your server that MEF will create for you accordingly:

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

An appropriate NUnit test to verify that adapters are created and that they are different instances:

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

If all of this helps you to some extent, we can also see how you want to configure what and how to create an instance using app.config.

+4
source

All Articles