Importing multiple extension assemblies in C# with MEF
Join the DZone community and get the full member experience.
Join For FreeAn application rarely depends on one single extension. If it does, then it has something wrong with its extension model and maybe it doesn’t need one after all. An extensible application allows the use of a multiple extensions that satisfy the standard exposed by the SDK:
In my previous article I showed how to use MEF to import a single external assembly. However, MEF doesn’t impose a limit on the number of assemblies that can be imported, although some conventions should be followed.
The extension standard is defined by a single class library with the following contents:
namespace PluginDevKit { public interface IWriter { void DisplayMessage(string message); } }
I will create two new class libraries that will represent two different extensions for the base application. For the first extension, the implementation will be:
using PluginDevKit; using System.ComponentModel.Composition; using System.Diagnostics; namespace FirstPlugin { [Export(typeof(IWriter))] public class FirstPlugin : IWriter { public void DisplayMessage(string message) { Debug.Print("First Plugin: " + message); } } }
Here, I am implementing the IWriter interface and exporting the main class that will later on be used by the main application.
The second extension is implemented the same way, with minor naming changes and it displays a different message:
using PluginDevKit; using System.ComponentModel.Composition; using System.Diagnostics; namespace SecondPlugin { [Export(typeof(IWriter))] public class SecondPlugin : IWriter { public void DisplayMessage(string message) { Debug.Print("Second Plugin: " + message); } } }
Now let’s look at the application itself, that will consume the extensions above. It’s structure is a bit different compared to the one that imports only a single assembly.
First of all, the property that is used to import is no longer a single instance:
internal IEnumerable<IWriter> Writer { get; set; }
There are multiple instances of IWriter to be loaded, therefore those are stored in an instance of IEnumerable, that will later on allow to go through each one of them to access their functionality.
There is also an interesting aspect when it comes to setting the Import attribute. The standard Import attribute only allows one Export to be assigned to the property that satisfies the condition set by the contract. If there is more than one Export present that satisfies the same contract, a CompositionException will be thrown when the composition will be attempted. Prior to the current release, the Import attribute could’ve been used to set a collection that will contain multiple instances of satisfactory Exports. In the current release, this is not allowed.
Instead, the ImportMany attribute should be used.
[ImportMany(typeof(IWriter))] internal IEnumerable<IWriter> Writer { get; set; }
This attribute automatically implies that there are multiple Exports that satisfy the defined Import.
When loading a single assembly, or when all extensions are registered in the same assembly, an instance of AssemblyCatalog can be used to parse the assembly. When there are multiple assemblies, this approach can no longer be used and an instance of AggregateCatalog should be used instead. It represents a collection of AssemblyCatalog instances (via the Catalogs property) that can be used to parse and compose multiple assemblies at once.
var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom(Application.StartupPath + @"\Plugin.dll"))); catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom(Application.StartupPath + @"\Plugin2.dll")));
I copied both extensions in the application folder, so I can easily access them. It is a good practice to keep all extension components in a single location that is relative to the application.
The rest of the composition process is similar to the one when a single assembly is used:
var container = new CompositionContainer(catalog); var batch = new CompositionBatch(); batch.AddPart(this); container.Compose(batch); foreach(IWriter w in Writer) w.DisplayMessage("DD");
Once compiled, in the Output window you can see that both extensions have been activated and the DisplayMessage method was successfully called.
Opinions expressed by DZone contributors are their own.
Comments