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

时间:2016-02-28 13:01:41

标签: c# plugins mef

所以我有一个相当愚蠢的问题,但我无法想出一个非常好的方法来解决这个问题"困境"。

我正在使用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只对相对较小的接口项目有一个硬依赖,而不需要(可能很大的)实现库。总比没有好,但仍然没有让我成为一个完全可选的"依赖性。

我只是想过这个吗?!有没有最佳做法来处理这样的问题?

1 个答案:

答案 0 :(得分:2)

tldr;你可能过度思考了这个问题:)

长版

  

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

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

  

有没有可行的方法来拥有这样的可选依赖项而不需要主应用程序可以访问可选的依赖项dll?

只要将dll部署到MainModule bin,MainModule就不需要引用StatusBarModule;然后可以构建DirectoryCatalog以组成MEF容器。当您使用"插件"时,无论如何都要使用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接口定义共享接口。依赖项/具体实现在运行时通过DirectoryCatalog解析。

底层代码/实现如下。请注意,可选依赖项有2个选项,其中一个(MyPlugin)使用.ctor注入而另一个(MyOtherPlugin)使用属性注入:

<强> MainModule

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

<强> CommonModule

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!" );
        }
    }
}

<强> StatusBarModule

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