9

我正在考虑使用 MEF 来解决插件管理要求。在简介中它说“没有硬依赖”,但据我所知,对导入/导出接口存在硬依赖。

我关心的是这个。我的可扩展应用程序是由我编写的。插件由第三方编写。因此,假设我们都从 V1 开始。我的应用程序定义了IPlugin插件“部件”需要实现的接口。我们部署应用程序,用户安装一堆第三方插件。一切都很好。

现在我升级了我的应用程序,我想在插件界面中添加一个新方法。我看到它的方式有两个选择:

  1. 编辑接口 - 可能不好,这会破坏现有插件,因为它们将不再正确实现接口。
  2. 创建一个新的“V2”接口,继承自原始接口

    公共接口 IPluginV2 : IPlugin {}

现在我有一个问题。我的用户都有一堆 3rd 方插件实现IPlugin,但我现在要求他们实现 IPluginV2。我认为这些 3rd 方插件将不再工作,直到开发人员实现新接口。

MEF 有办法处理这种情况吗?我真的在寻找一种方法,让我在让旧插件继续工作而无需重建的同时改进我的应用程序。最好的处理方法是什么?

4

2 回答 2

15

对于版本控制,您可能希望每个版本都有一个接口,并在它们之间使用适配器模式。它是System.AddIn处理版本控制的方式,它也适用于 MEF。

假设您的应用程序的V1有以下类型:

public interface IPlugin
{
    string Name { get; }
    string Publisher { get; }
    string Version { get; }

    void Init();
}

这是我们V1插件感知应用程序的唯一合同。它包含在 assembly 中Contracts.v1

然后我们有一个 V1插件:

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

导出为IPlugin. 它包含在程序集中Plugin.v1并发布在主机应用程序基本路径下的“插件”文件夹中。

最后是V1主机:

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

它导入IPlugin文件夹“插件”中的所有部件。

然后我们决定发布V2,因为我们想提供版本控制,所以我们需要无版本合约:

public interface IPluginV2
{
    string Name { get; }
    string Publisher { get; }
    string Version { get; }
    string Description { get; }

    void Init(IHost host);
}

具有新属性和修改的方法签名。另外,我们为主机添加了一个接口:

public interface IHost
{
    //Here we can add something useful for a plugin.
}

这两个都包含在 assembly 中Contracts.v2

为了允许版本控制,我们添加了一个从 V1 到 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(); }
}

这只是适应从IPluginIPluginV2。它返回一个固定的描述,并且Init它对主机参数不做任何事情,但它InitV1合约中调用无参数。

最后是V2主机:

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

它同时导入IPluginIPluginV2部分,将每个部分适应IPluginIPluginV2 公开所有已发现插件的串联序列。适配完成后,所有插件都可以作为V2插件处理。

您还可以在主机接口上使用适配器模式,以允许V2插件与V1主机一起工作。

另一种方法是autofac IoC,它可以与 MEF集成并支持使用适配器进行版本控制。

于 2013-02-25T19:54:02.367 回答
1

只是一些建议(我没有测试过)可能有助于为您集思广益的解决方案:

  1. 如果使用 MEF,AggregateCatalog请为每个版本使用不同的版本。这样您就可以同时维护 V1 和 V2 插件可用

  2. 如果不使用 MEF,则来自第三方的动态加载 DLL 应返回它已实现的当前接口版本,您可以根据版本号选择可以进行的调用

那有帮助吗?

干杯,二村

于 2013-02-25T10:04:40.633 回答