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.