是的,你应该这样做。如果您将模块导出为它们自己的实现类型而不是接口,则导入模块的使用模块需要引用包含模块实现的库。使用 IoC 的主要原因之一就是避免这种情况。
是的,您应该有一个容器。如果您的模块包含容器,您将无法使用它导出/导入此模块,因为您需要在容器存在之前拥有该模块的实例。这里没有真正的 MEF 特定问题,它与 Unity 或其他任何东西相同。对于您的 PRISM 应用程序,其想法是将模块的实例化和连接的关注点分离到一个地方,即容器。容器在引导程序中的任何其他内容之前创建,然后创建外壳、模块、服务以及您需要的任何内容。在您的应用程序中使用其他 IoC 容器来管理在完全不同的上下文中的对象的实例化和引用是有意义的,假设不是为了您的 UI,而是为了将复杂的业务对象连接在一起。此外,可能在 主容器 不知道的内部(私有)容器中使用 MEF 在构造时自行组装模块是有意义的。比你有一个 composite UI 的模块本身就是 composite UIs。如果您真的需要,请仔细考虑。你很容易遇到这种东西的问题,例如两次加载程序集等等等等。
和以前一样。 Module B 引用 ModuleA 的接口项目,然后导入 IModuleA 类型的字段或参数。容器将解析依赖注入 ModuleA。
如前所述,您真的应该让您的架构保持清晰。如果要在模块之间注入依赖项,它们应该在同一个容器中。这就是 IoC 的理念。
我正在开发一个包含多个 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 => 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