I'm thinking of using MEF to solve a plugin management requirement. In the blurb it says "no hard dependencies" but as far as I can see, there is a hard dependency on the import/export interface.
My concern is this. My extendable app is written by me. Plugins are written by third parties. So lets say we all start off with V1. My app defines a IPlugin
interface that the plugin 'parts' need to implement. We deploy the app and users install a bunch of third party plugins. All well and good.
Now I upgrade my app and I want to add a new method to the plugin interface. The way I see it I have 2 choices:
Create a new 'V2' interface, that inherits from the original
public interface IPluginV2 : IPlugin {}
Now I have a problem. My users all have a bunch of 3rd party plugins implementing IPlugin
, but I now require them to implement IPluginV2. I presume these 3rd party plugins will no longer work, until the developers implement the new interface.
Does MEF have a way to handle this situation? I'm really looking for a way that lets me evolve my app while having old plugins continue to work without having to be rebuilt. Whats the best way of handling that?
The Managed Extensibility Framework or MEF is a library for creating lightweight, and extensible applications. It allows application developers to discover and use extensions with no configuration required. It also lets extension developers easily encapsulate code and avoid fragile hard dependencies.
MEF is more than just dependency injection techniques. It is used wherein we need a plugin-based architecture for our application, but at the same time MEF uses an IoC-based approach for dependency injection.
For those who don't know, the Managed Extensibility Framework (MEF) is alive and well, and has been ported to . NET Core as System. Composition (source here).
For versioning, you will probably want an interface for each version and the adapter pattern to go between them. It is how System.AddIn
handles versioning, and it works for MEF, too.
Let's say we have the following types for the V1 of your application:
public interface IPlugin
{
string Name { get; }
string Publisher { get; }
string Version { get; }
void Init();
}
This is the only contract for our V1 plugin-aware app. It is contained in assembly Contracts.v1
.
Then we have a V1 plugin:
[Export(typeof(IPlugin))]
public class SomePlugin : IPlugin
{
public string Name { get { return "Some Plugin"; } }
public string Publisher { get { return "Publisher A"; } }
public string Version { get { return "1.0.0.0"; } }
public void Init() { }
public override string ToString()
{
return string.Format("{0} v.{1} from {2}", Name, Version, Publisher);
}
}
Which is exported as IPlugin
. It is contained in assembly Plugin.v1
and is published on the "plugins" folder under the application base path of the host.
Finally the V1 host:
class Host : IDisposable
{
CompositionContainer _container;
[ImportMany(typeof(IPlugin))]
public IEnumerable<IPlugin> Plugins { get; private set; }
public Host()
{
var catalog = new DirectoryCatalog("plugins");
_container = new CompositionContainer(catalog);
_container.ComposeParts(this);
}
public void Dispose() { _container.Dispose(); }
}
which imports all IPlugin
parts found in folder "plugins".
Then we decide to publish V2 and because we want to provide versioning we will need versionless contracts:
public interface IPluginV2
{
string Name { get; }
string Publisher { get; }
string Version { get; }
string Description { get; }
void Init(IHost host);
}
with a new property and a modified method signature. Plus we add an interface for the host:
public interface IHost
{
//Here we can add something useful for a plugin.
}
Both of these are contained in assembly Contracts.v2
.
To allow versioning we add a plugin adapter from V1 to V2:
class V1toV2PluginAdapter : IPluginV2
{
IPlugin _plugin;
public string Name { get { return _plugin.Name; } }
public string Publisher { get { return _plugin.Publisher; } }
public string Version { get { return _plugin.Version; } }
public string Description { get { return "No description"; } }
public V1toV2PluginAdapter(IPlugin plugin)
{
if (plugin == null) throw new ArgumentNullException("plugin");
_plugin = plugin;
}
public void Init(IHost host) { plugin.Init(); }
public override string ToString() { return _plugin.ToString(); }
}
This simply adapts from IPlugin
to IPluginV2
. It returns a fixed description and in the Init
it does nothing with the host argument but it calls the parameterless Init
from the V1 contract.
And finally the V2 host:
class HostV2WithVersioning : IHost, IDisposable
{
CompositionContainer _container;
[ImportMany(typeof(IPluginV2))]
IEnumerable<IPluginV2> _pluginsV2;
[ImportMany(typeof(IPlugin))]
IEnumerable<IPlugin> _pluginsV1;
public IEnumerable<IPluginV2> Plugins
{
get
{
return _pluginsV1.Select(p1 => new V1toV2PluginAdapter(p1)).Concat(_pluginsV2);
}
}
public HostV2WithVersioning()
{
var catalog = new DirectoryCatalog("plugins");
_container = new CompositionContainer(catalog);
_container.ComposeParts(this);
}
public void Dispose() { _container.Dispose(); }
}
which imports both IPlugin
and IPluginV2
parts, adapts each IPlugin
into IPluginV2
and exposes a concatenated sequence of all discovered plugins. After the adaptation is completed, all plugins can be treated as V2 plugins.
You can also use the adapter pattern on the interface of the host to allow V2 plugins to work with V1 hosts.
Another approach would be the autofac IoC that can integrate with MEF and can support versioning using adapters.
Just a couple of suggestions (which I have not tested) that may help brainstorming a solution for you:
If using MEF, use different AggregateCatalog
for each of the versions. That way you could maintain both V1 and V2 plugins available
If not using MEF, a dynamic loaded DLL from the third party should return the current interface version it has implemented and you can choose which calls you could make depending on the version number
Did that help?
Cheers, dimamura
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With