2

我正在编写一个 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. 如何最好地在具有大量模块的应用程序中保持快速性能。
4

1 回答 1

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

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

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

  4. 如前所述,你真的应该让你的架构直截了当。如果要在模块之间注入依赖项,它们应该在同一个容器中。这就是国际奥委会的想法。

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

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

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

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

自举

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

导出视图(模块)

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

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

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

    [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(); 数据上下文 = 视图模型;} }

[ViewExport] 属性

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

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

通过调用[Export]type of的构造函数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; }
}

导入视图

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

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

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

最后一步是将视图插入区域,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 => 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 的截屏视频

于 2013-04-04T07:27:37.600 回答