Overview
MEF (Managed Extensibility Framework) was used for the plugin implementation. A description of how the plugin system works is given below with information on both the MEF and our own implementation, for more information on MEF go to the official documentation site or the MEF Community Site. Our implementation of the plugins is slightly different from the one given in the official documentation, in order to avoid adding unnecessary components. Obviously, the concepts and principles of MEF are still followed in our implementation.
Plugin Components
As of yet there is no separate module in our solution for plugins, it has not been necessary as MEF provides everything that’s needed. Our plugin scheme is composed of a plugin container and the plugins themselves.
The plugin container has two main roles:
- to provide the contract for plugins to be built on
- to instantiate and expose the plugin use outside the container
The contract of a plugin consists of the service contract and the metadata. The service contract exposes the properties and methods of the plugin while the metadata is used to expose specific data.
In the case of storage providers the plugin contract is IStorageProvider which exposes operations such as Initialize, Authorize and others.
The metadata for storage providers is IStorageProviderMetadata which exposes two fields:
/// <summary>
/// Use this type of interface to provide data that the plugins have to expose using the
/// [ExportMetadata("Name", VALUE)] attribute.
/// </summary>
public interface IStorageProviderMetadata
{
/// <summary>
/// The provider name.
/// </summary>
ProviderNames Name { get; }
/// <summary>
/// The key used to save the account name in the configuration of a provider.
/// </summary>
string GatewayAccountKey { get; }
}
A plugin has to export both the service contract and the metadata (in the case of the metadata each field has to be exported individually):
[Export(typeof(IStorageProvider))]
[ExportMetadata("Name", ProviderNames.Test)]
[ExportMetadata("GatewayAccountKey", "TestGatewayStorageProcessor")]
public class TestStorage : IStorageProvider
Assembling Plugin Components
The assembling of the plugin components is done in the plugin container which has to display the wanted plugins by importing their contracts:
//In order to be extensible, we need to import a list of plugins.
//An ordinary ImportAttribute attribute is filled by one and only one ExportAttribute.
//If more than one is available, the composition engine produces an error.
//To create an import that can be filled by any number of exports, you can use the ImportManyAttribute attribute.
[ImportMany]
//Each Lazy<T, TMetadata> contains an IStorageProvider object, representing an actual plugin, and an IStorageProviderMetadata object, representing its metadata.
public IEnumerable<Lazy<IStorageProvider, IStorageProviderMetadata>> _plugins;
The plugin container assembles the plugins components by use of a CompositionContainer:
private CompositionContainer _container;
The CompositionContainer is pointed towards the components (or their location) through catalogs:
- The plugin container which has to be loaded with the plugins (this request is made through the _plugins field with the ImportMany attribute)
- The plugins themselves
//An aggregate catalog that combines multiple catalogs
var catalog = new AggregateCatalog();
//Adds all the parts found in the same assembly as the Program class
catalog.Catalogs.Add(new AssemblyCatalog(typeof(StorageBridgeService).Assembly));
//------------------------------------------------------------------------------
//get components from the assemblies themselves
//the given directory is searched for components and they will be loaded automatically based on the imports
//the second parameter filters out unnecessary dlls to save time
catalog.Catalogs.Add(new DirectoryCatalog(Directory.GetCurrentDirectory(), "Plugin.*.dll"));
//Create the CompositionContainer with the parts in the catalog
_container = new CompositionContainer(catalog);
//Fill the imports of this object
//this call should load the available plugins
this._container.ComposeParts(this);
If no problem appears then the _plugins field should be loaded with the available plugins from the current directory whose names follow the given pattern.
In order to add more plugins no changes need be done to the container, they will be discovered and loaded automatically.