MatthiasG 展示了在 MEF 中定义模块的方法。需要注意的是,视图本身并不实现 IModule 接口。然而,使用 MEF 和 PRISM 的有趣之处在于如何在启动时将模块导入到您的 UI 中。
我只能在这里原则上解释系统,但它可能指向正确的方向。对于任何事情,总是有无数的方法,但这就是我理解为最佳实践并且我有非常好的经验:
引导
与 Prism 和 Unity 一样,一切都始于 Bootstrapper,它派生自 Microsoft.Practices.Prism.MefExtensions
中的 MefBootstrapper
。引导程序设置了 MEF 容器,从而导入所有类型,包括服务、视图、ViewModel 和模型。
导出视图(模块)
这是 MatthiasG 提到的部分。我的做法是使用以下结构来处理 GUI 模块:
该模型导出其本身的具体类型(也可以是接口,参见 MatthiasG),使用 [Export(typeof(MyModel)]
属性。使用 [PartCreationPolicy(CreationPolicy.Shared)]
标记表示只创建一个实例(单例行为)。
ViewModel 就像模型一样导出其本身的具体类型,并通过构造函数注入导入模型:
[ImportingConstructor]
public class MyViewModel(MyModel model)
{
_model = model;
}
View 通过构造函数注入导入 ViewModel,与 ViewModel 导入模型的方式相同。
现在,这很重要:视图使用特定属性导出自己,该属性派生自“标准”[Export]
属性。以下是一个示例:
[ViewExport(RegionName = RegionNames.DataStorageRegion)]
public partial class DataStorageView
{
[ImportingConstructor]
public DataStorageView(DataStorageViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
[ViewExport] 属性
[ViewExport]
属性有两个作用:由于它派生自 [Export]
属性,它告诉 MEF 容器导入视图。作为什么?这隐藏在它的定义中:构造函数签名如下:
public ViewExportAttribute() : base(typeof(UserControl)) {}
通过使用
[Export]
的构造函数并指定
UserControl
类型,每个视图都将在MEF容器中注册为
UserControl
。
其次,它定义了一个属性
RegionName
,该属性将用于确定视图应插入Shell UI的哪个区域。RegionName属性是接口
IViewRegionRegistration
的唯一成员。这个特性类:
[Export(typeof(IViewRegionRegistration))]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[MetadataAttribute]
public sealed class ViewExportAttribute : ExportAttribute, IViewRegionRegistration
{
public ViewExportAttribute() : base(typeof(UserControl)) {}
public string RegionName { get; set; }
}
导入视图
现在,系统的最后一个关键部分是一种行为,它可以附加到您的 shell 的区域上: AutoPopulateExportedViews
行为。此行代码将从 MEF 容器中导入您的模块:
[ImportMany]
private Lazy<UserControl, IViewRegionRegistration>[] _registeredViews;
如果容器中的类型拥有实现了IViewRegionRegistration
接口的元数据属性,并且已经注册为UserControl
类型,则此处导入所有类型。因为您的[ViewExport]
属性是这样的,这意味着您会导入每个标有[ViewExport(...)]
的类型。
最后一步是将视图插入到区域中,在其OnAttach()
属性中完成此操作:
[Export(typeof(AutoPopulateExportedViewsBehavior))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification
{
protected override void OnAttach()
{
AddRegisteredViews();
}
public void OnImportsSatisfied()
{
AddRegisteredViews();
}
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 的区域:
protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
ViewModelInjectionBehavior.RegionsToAttachTo.Add(RegionNames.ElementViewRegion);
var behaviorFactory = base.ConfigureDefaultRegionBehaviors();
behaviorFactory.AddIfMissing("AutoPopulateExportedViewsBehavior", typeof(AutoPopulateExportedViewsBehavior));
}
我们已经来了一圈,我希望这能让你了解MEF和PRISM是如何安排好一切的。
如果你还没有厌倦:
Mike Taulty的视频演示