MEF 和可选插件依赖项 - 最佳实践?

MEF & optional plugin dependencies - best practice?

所以我有一个相当愚蠢的问题,但我想不出解决这个问题的好方法"dilemma"。

我正在使用 MEF 开发一个应用程序,并试图想出一种处理可选依赖项的好方法。

随机示例,假设有两个插件; "MyPlugin",以及一个可能会或可能不会被主应用程序加载的 StatusBar 插件。 MyPlugin 应该对状态栏管理器具有可选的依赖性;如果该插件由 MEF 加载,它将使用它向状态栏输出一些内容;如果没有,它根本不会。

[ModuleExport(typeof(MyPlugin))]
public class MyPlugin : IModule
{
  [ImportingConstructor]
  public MyPlugin([Import(AllowDefault = true)] StatusBarManager optionalStatusBarManager OptionalStatusBar)
  {
    if(optionalStatusBarManager != null)
      optionalStatusBarManager.Display("Hi from MyPlugin!");
  }
}

为了识别类型 "StatusBarManager",MyPlugin 项目需要引用 StatusBarManager 项目/它的 dll。

现在根据我的直觉,如果 MyPlugin 需要 StatusBarManager 的 dll 无论如何都可以访问,那么拥有一个可选的依赖关系毫无意义,因为它依赖于它的公开类型。

但是我有什么选择呢?有没有什么好的方法可以拥有这样的可选依赖项而不需要主应用程序访问可选依赖项的 dll?

到目前为止,我只能想出使用 "dynamic" 类型按合同名称导入这些。但是我显然会在 MyPlugin 中失去所有编译时安全性。

或者我可以创建另一个项目,例如只包含接口定义的 StatusBarManagerAbstractions。那么至少 MyPlugin 只会硬依赖相对较小的接口项目,而不需要(可能很大的)实现库。总比没有好,但仍然没有让我觉得完全 "optional" 依赖。

我是不是想多了?!是否有处理此类问题的最佳实践?

tldr;你可能想多了:)

长版

I'm developing an application with MEF and trying to come up with a good way to handle optional dependencies.

由于依赖项是可选的,当前的示例代码应该足够了。但是,作为一般的最佳实践,所有依赖项都将按接口而不是具体类型进行引用/编码。在MainModule中的依赖解析过程中,CommonModule中定义的接口的实现由MEF容器通过程序集引用、文件目录扫描等方式解析...并根据需要构建依赖图。

Is there any decent way to have such optional dependencies without requiring the main application to have access to the optional dependency's dlls?

只要将dll部署到MainModule bin中,MainModule就不需要引用StatusBarModule;然后可以构建一个 DirectoryCatalog 来组成 MEF 容器。当您使用 "plugins" 时,您无论如何都想使用 DirectoryCatalog,以便可以在运行时发现 dll。

例如,根据给定的场景,解决方案结构应如下所示,其中删除了除 CommonModule 之外的所有对 MainModule 的引用:

Solution
- MainModule
    - starts the app (console, winform, wpf, etc...)
    - references: CommonModule (no other project references)
- CommonModule
    - references: no other modules
    - purpose: provides a set of common interfaces all projects can reference
    - note: the actual implementations will be in other projects
- MyPluginModule
    - references: CommonModule
    - purpose: provides plugins for the app
    - Implements interfaces from CommonModule
    - note: uses dependencies defined by interfaces in CommonModule and implemented by other modules
    - build: build step should copy the dll to the MainModule's bin
- StatusBarModule
    - references: CommonModule
    - purpose: provides plugin dependencies required by the application
    - note: uses dependencies defined by interfaces in CommonModule and implemented by other modules
    - build: build step should copy the dll to the MainModule's bin

MyPluginModule 和 StatusBarModule 几乎相同,两者都没有相互引用;相反,它们通过 CommonModule 接口定义共享接口。 dependencies/concrete 实现在运行时通过 DirectoryCatalog 解析。

基础 code/implementations 如下。请注意,可选依赖项有 2 个选项,其中一个 (MyPlugin) 使用 .ctor 注入,而另一个 (MyOtherPlugin) 使用 属性 注入:

主模块

class Program
{
    static void Main()
    {
        // will output 'Hi from MyPlugin!' when resolved.
        var myPlugin = MefFactory.Create<IMyPlugin>().Value;

        // will output 'Hi from MyOtherPlugin!' when resolved.
        var myOtherPlugin = MefFactory.Create<IMyOtherPlugin>().Value;

        Console.ReadLine();
    }
}

public static class MefFactory
{
    private static readonly CompositionContainer Container = CreateContainer();

    public static Lazy<T> Create<T>()
    {
        return Container.GetExport<T>();
    }

    private static CompositionContainer CreateContainer()
    {
        // directory where all the dll's reside.
        string directory = AppDomain.CurrentDomain.BaseDirectory;

        var container = new CompositionContainer( new DirectoryCatalog( directory ) );

        return container;
    }
}

公共模块

public interface IModule { }
public interface IMyPlugin { }
public interface IMyOtherPlugin { }
public interface IStatusBarManager
{
    void Display( string msg );
}

MyPluginModule

[Export( typeof( IMyPlugin ) )]
internal class MyPlugin : IModule, IMyPlugin
{
    private readonly IStatusBarManager _manager;

    [ImportingConstructor]
    public MyPlugin( [Import( AllowDefault = true )] IStatusBarManager manager )
    {
        _manager = manager;
        if( _manager != null )
        {
            _manager.Display( "Hi from MyPlugin!" );
        }
    }
}

[Export( typeof( IMyOtherPlugin ) )]
internal class MyOtherPlugin : IModule, IMyOtherPlugin
{
    private IStatusBarManager _statusManager;

    [Import( AllowDefault = true )]
    public IStatusBarManager StatusManager
    {
        get { return _statusManager; }
        private set
        {
            _statusManager = value;
            _statusManager.Display( "Hi from MyOtherPlugin!" );
        }
    }
}

状态栏模块

[Export( typeof( IStatusBarManager ) )]
internal class StatusBarManager : IStatusBarManager
{
    public void Display( string msg )
    {
        Console.WriteLine( msg );
    }
}