共享MEF部件/导出模块之间的可重用性

Muh*_*eed 2 modularity mef ioc-container inversion-of-control unity-container

我正在写一个WPF MEF应用程序.在过去,我已经编写了基于WPF IoC的应用程序,并使用PRISM构建代码模块,如下所示:

  • Shell - 主要可执行文件
  • BusinessArea.Module.Interface - 包含BusinessArea模块的Services,ViewModel和Views的所有接口.
  • BusinessArea.Module - 包含PRISM IModule的实现,并实现BusinessArea.Module.Interface项目中的接口.
  • OtherBusinessArea.Module.Interface - 另一个模块
  • OtherBusinessArea.Module - 另一个模块接口

在IoC领域,每个模块在加载时都会使用IoC容器注册其组件.如果我希望我的两个模块能够重用彼此的组件,他们只需要引用我的一个接口项目并注入组件.

但是在MEF中,我无法找到任何将部件分离到模块中以及这种类型的模块间组件重用的良好实践或指南.我有五个问题:

  1. 我应该继续为我的所有部件创建接口吗?
  2. 我应该在整个应用程序中有一个共享容器,还是每个模块有一个共享容器(每个模块都是从Windows 8样式的开始菜单启动的单独应用程序).
  3. 如果一个模块想要使用另一个模块中的一个部件,我该如何保持分离.
  4. 如果一个模块想要在另一个容器中使用一个部件,我该如何保持分离.
  5. 如何在具有大量模块的应用程序中最好地保持性能.

Mar*_*arc 7

  1. 是的你应该.如果将模块导出为自己的实现类型而不是接口,则导入模块的使用模块需要引用包含模块实现的库.使用IoC的主要原因之一就是避免这种情况.

  2. 是的,你应该有一个容器.如果您的模块包含容器,您将无法使用它导出/导入此模块,因为您需要在容器存在之前拥有模块的实例.这里没有真正的MEF特定问题,它与Unity或其他任何东西相同.对于您的PRISM应用程序,我们的想法是分离在一个地方(即容器)实例化和连接模块的问题.容器在Bootstrapper中的任何其他内容之前创建,然后创建shell,模块,服务以及您需要的任何内容.在您的应用程序中使用其他IoC容器是有意义的,这些容器在完全不同的上下文中管理对象的实例化和引用,假设不是为了您的UI,而是为了将复杂的业务对象连接在一起.此外,它可能是有意义的组装您的模块自身建设与MEF在内部(私有)容器中的主容器不知道.比你有一个复合UI,模块本身就是复合UI.如果你真的需要它,请彻底考虑它.你很容易遇到这类东西的问题,例如装载组件两次等等.

  3. 和以前一样.模块B引用ModuleA的接口项目,然后导入IModuleA类型的字段或参数.容器将解析依赖关系以注入ModuleA.

  4. 如前所述,你应该真正拥有自己的架构.如果要在模块之间注入依赖关系,它们应该在同一个容器中.这就是IoC的想法.

  5. 我正在开发一个包含多个IoC容器的复杂应用程序.我使用MEF作为UI,这是shell和一些UI模块.对于更多与业务逻辑相关的东西,我使用AutoFac IoC容器.主要是因为Autofac是一个"真正的"IoC容器而MEF不是,但也因为它更快.Autofac可以执行MEF可以执行的任何操作,下次我也会使用Autofac而不是MEF来创建UI.

很多问题的问题....

以下是我刚才提出的类似问题的答案,希望对您有所帮助:

我原则上只能解释系统,但它可能会指向正确的方向.对于一切都有很多方法,但这是我所理解的最佳实践以及我所获得的非常好的经验:

引导

与Prism和Unity一样,这一切都始于Bootstrapper,它源自MefBootstrapperMicrosoft.Practices.Prism.MefExtensions.引导程序设置MEF容器,从而导入所有类型,包括服务,视图,ViewModel和模型.

导出视图(模块)

这是MatthiasG所指的部分.我的做法是GUI模块的以下结构:

  • 模型使用[Export(typeof(MyModel)]属性将其自身导出为具体类型(也可以是接口,请参阅MatthiasG).标记[PartCreationPolicy(CreationPolicy.Shared)]为表示只创建了一个实例(单例行为).

  • ViewModel将其自身导出为与模型类似的具体类型,并通过构造函数注入导入Model:

    [ImportingConstructor] public class MyViewModel(MyModel model){_ model = model; }

  • View通过构造函数注入导入ViewModel,与ViewModel导入Model的方式相同

  • 现在,这很重要:View使用特定属性导出自身,该属性派生自"标准" [Export]属性.这是一个例子:

    [ViewExport(RegionName = RegionNames.DataStorageRegion)] public partial class DataStorageView {[ImportingConstructor] public DataStorageView(DataStorageViewModel viewModel){InitializeComponent(); DataContext = viewModel; }}

[ViewExport]属性

[ViewExport]属性做两件事:因为它派生自[Export]属性,它告诉MEF容器导入View.什么?这隐藏在它的定义中:构造函数签名如下所示:

public ViewExportAttribute() : base(typeof(UserControl)) {}
Run Code Online (Sandbox Code Playgroud)

通过调用[Export]类型为的构造函数UserControl,每个视图都UserControl在MEF容器中注册.

其次,它定义了一个属性RegionName,该属性稍后将用于决定应该在哪个Shell UI区域中插入视图.RegionName属性是接口的唯一成员IViewRegionRegistration.属性类:

/// <summary>
/// Marks a UserControl for exporting it to a region with a specified name
/// </summary>
[Export(typeof(IViewRegionRegistration))]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[MetadataAttribute]
public sealed class ViewExportAttribute : ExportAttribute, IViewRegionRegistration
{
    public ViewExportAttribute() : base(typeof(UserControl)) {}

    /// <summary>
    /// Name of the region to export the View to
    /// </summary>
    public string RegionName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

导入视图

现在,系统的最后一个关键部分是一个行为,它附加到shell的区域:AutoPopulateExportedViews行为.这将使用以下行从MEF容器导入所有模块:

[ImportMany] 
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
Run Code Online (Sandbox Code Playgroud)

UserControl如果它们具有实现的元数据属性,则会导入从容器注册的所有类型IViewRegionRegistration.因为您的[ViewExport]属性具有,这意味着您导入标记为的每个类型[ViewExport(...)].

最后一步是将Views插入区域,bahvior在其OnAttach()属性中执行:

/// <summary>
/// A behavior to add Views to specified regions, if the View has been exported (MEF) and provides metadata
/// of the type IViewRegionRegistration.
/// </summary>
[Export(typeof(AutoPopulateExportedViewsBehavior))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification
{
    protected override void OnAttach()
    {
        AddRegisteredViews();
    }

    public void OnImportsSatisfied()
    {
        AddRegisteredViews();
    }

    /// <summary>
    /// Add View to region if requirements are met
    /// </summary>
    private void AddRegisteredViews()
    {
        if (Region == null) return;

        foreach (var view in _registeredViews
            .Where(v => v.Metadata.RegionName == Region.Name)
            .Select(v => v.Value)
            .Where(v => !Region.Views.Contains(v)))
            Region.Add(view);

    }

    [ImportMany()] 
    private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
}
Run Code Online (Sandbox Code Playgroud)

注意事项.Where(v => v.Metadata.RegionName == Region.Name).这使用属性的RegionName属性来仅获取为特定区域导出的那些视图,并将行为附加到.

该行为将附加到引导程序中shell的区域:

protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors(){ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);

var behaviorFactory = base.ConfigureDefaultRegionBehaviors();
behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior));
Run Code Online (Sandbox Code Playgroud)

}

我希望,这可以让你了解MEF和PRISM如何落实到位.

并且,如果你仍然不感到无聊:这是完美的:

Mike Taulty的截屏视频