【问题标题】:Sharing MEF Parts/Exports for Reusability Between Modules共享 MEF 部件/导出以实现模块之间的可重用性
【发布时间】:2013-03-25 08:39:27
【问题描述】:

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

  • Shell - 主要可执行文件
  • BusinessArea.Module.Interface - 包含 BusinessArea 模块的服务、视图模型和视图的所有接口。
  • BusinessArea.Module - 包含 PRISM IModule 的实现,并在 BusinessArea.Module.Interface 项目。
  • OtherBusinessArea.Module.Interface - 另一个模块
  • OtherBusinessArea.Module - 另一个模块接口

在 IoC 世界中,每个模块在加载时都会向 IoC 容器注册其组件。如果我希望我的两个模块相互重用组件,他们只需要引用我的一个接口项目并注入组件即可。

但是,在 MEF 中,我真的找不到任何好的做法或指南来将部件分成模块以及这种类型的模块间重用组件。我有五个问题:

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

【问题讨论】:

  • 我是否应该使用基础 IBusinessAreaModule 来导入业务模块的外壳?我正在对具有多个业务领域的大型 Delphi 应用程序进行增量重写。我们的策略基本上是先复制每个业务领域的最小值,然后慢慢添加。

标签: inversion-of-control unity-container mef ioc-container modularity


【解决方案1】:
  1. 是的,你应该这样做。如果您将模块导出为它们自己的实现类型而不是接口,则导入模块的使用模块需要引用包含模块实现的库。使用 IoC 的主要原因之一就是避免这种情况。

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

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

  4. 如前所述,您真的应该让您的架构保持清晰。如果要在模块之间注入依赖项,它们应该在同一个容器中。这就是 IoC 的理念。

  5. 我正在开发一个包含多个 IoC 容器的复杂应用程序。我将 MEF 用于 UI,即 shell 和几个 UI 模块。对于更多与业务逻辑相关的东西,我使用 AutoFac IoC 容器。主要是因为 Autofac 是一个“真正的”IoC 容器,而 MEF 不是,还因为它更快。 Autofac 可以做 MEF 可以做的任何事情,下次我也会在 UI 上使用 Autofac 而不是 MEF。

一个问题有很多问题......

这是我不久前提出的类似问题的答案,希望对您也有帮助:

我在这里只能从原理上解释系统,但它可能会为您指明正确的方向。每件事都有很多方法,但这是我理解的最佳实践,也是我在这方面取得了非常好的经验:

引导

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

导出视图(模块)

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

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

  • ViewModel 像模型一样将自身导出为具体类型,并通过构造函数注入导入模型:

    [导入构造函数] 公共类 MyViewModel(MyModel 模型) { _model = 模型; }

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

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

    [ViewExport(RegionName = RegionNames.DataStorageRegion)] 公共部分类 DataStorageView { [导入构造函数] 公共数据存储视图(数据存储视图模型视图模型) { 初始化组件(); 数据上下文 = 视图模型; } }

[ViewExport] 属性

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

public ViewExportAttribute() : base(typeof(UserControl)) {}

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

其次,它定义了一个属性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; }
}

导入视图

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

[ImportMany] 
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;

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

最后一步是将 View 插入区域,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;
}

通知.Where(v =&gt; v.Metadata.RegionName == Region.Name)。这使用属性的 RegionName 属性来仅获取为特定区域导出的那些视图,您正在将行为附加到。

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

受保护的覆盖 IRegionBehaviorFactory ConfigureDefaultRegionBehaviors() { ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);

var behaviorFactory = base.ConfigureDefaultRegionBehaviors();
behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior));

}

我们已经完成了一个完整的循环,我希望,这能让您了解 MEF 和 PRISM 是如何实现的。

而且,如果你还不觉得无聊:这是完美的:

Mike Taulty's screencast

【讨论】:

  • 这个答案值得更多的投票,只是因为它的全面性和范围。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-19
  • 1970-01-01
  • 2016-04-20
  • 2016-03-21
  • 1970-01-01
  • 1970-01-01
  • 2010-10-31
相关资源
最近更新 更多