Blendability 第 III 部分 – 使用 MEF 进行视图模型定位器替换





4.00/5 (3投票s)
Blendability 第 III 部分 – 使用 MEF 进行视图模型定位器替换
每个人都喜欢 MEF!你不喜欢吗?我认为 MEF 是 .NET Framework 4 最新推出的最棒的功能之一。
万一您不知道我在说什么,我强烈建议您开始阅读我同事 Bnaya Eshet 的 博客,里面有关于 MEF 的内容。他为初学者提供了很棒的 MEF 教程。
那么,为什么我在这个与 WPF 相关的帖子中还要写关于 MEF 的内容呢?嗯,MEF 是一个出色的框架,用于扩展性和对象组合,它还可以用作 DI 容器,支持声明式和命令式。
凭借我在架构多个 WPF 客户端应用程序方面的经验,我发现 MEF 非常适合,能让大家的生活更轻松。
下面是一个简单的示例,展示了如何在我的客户端应用程序中使用 MEF。
[Export(typeof(ICommandBarViewModel))]
public class CommandBarViewModel : NotificationObject, ICommandBarViewModel
{
[Import]
private ILogger Logger
{
get;
set;
}
[Import]
private IConfigurationService ConfigurationService
{
get;
set;
}
public IEnumerable<ICommandBarAction> Actions
{
get
{
return new ICommandBarAction[]
{
new CommandBarAction
{
Content = "Open",
Command = new DelegateCommand<object>(p => Open())
},
new CommandBarAction
{
Content = "Save",
Command = new DelegateCommand<object>(p => Save())
}
};
}
}
}
上面的示例代码演示了 MEF 的一个简单用法,用于将 CommandBarViewModel
与 Logger
和 Configuration Service 依赖项进行组合。
正如您所见,CommandBarViewModel
类型被 MEF 的 ExportAttribute
属性装饰,实现了 ICommandBarViewModel
。使用 MEF 的容器组合 CommandBarViewModel
类型的实例,可以满足其所有导入,即 ILogger
和 IConfigurationService
。
实现 MVVM 模式,尤其是以 View-first 的方式,需要将 ViewModel 实例注入到直接从 XAML 创建的相关 View 中。有一个称为 ViewModel 定位器(一种服务定位器模式)的流行模式,在这种情况下可能很有用。
就个人而言,我认为 ViewModel 定位器非常笨拙,因为您总是需要创建一个实例并将其放在资源字典中,然后使用 Binding
和 StaticResource
标记来解析它。由于您还需要提供一个关于要创建哪个 ViewModel 的提示,因此您应该在 Binding.Path
中设置 ViewModel 类型或其他提示,或者使用绑定转换器。最终会变成这样
<Application.Resources>
<t:ViewModelLocator x:Key="viewModelLocator"/>
<t:IndexerConverter x:Key="viewModelIndexerConverter" />
</Application.Resources>
DataContext="{Binding
Source={StaticResource viewModelLocator},
Converter={StaticResource viewModelIndexerConverter},
ConverterParameter=MainViewModel}"
Needless to say that the implementation of the view model locator itself should deal somehow with a ridiculous index provided as "MainViewModel
" hint to the indexer binding converter. (无需多言,ViewModel 定位器本身的实现需要以某种方式处理提供给索引器绑定转换器的、作为 "MainViewModel
" 提示的荒谬索引。)
使用我编写的一个简单的标记扩展和 MEF,您的 XAML 会变成这样
<UserControl x:Class="Blendability.Solution.Parts.CommandBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ts="http://blogs.microsoft.co.il/blogs/tomershamam"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"d:DesignHeight="76" d:DesignWidth="460"
DataContext="{ts:Import ts:ICommandBarViewModel}">
<ToolBar ItemsSource="{Binding Actions}">
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Style="{DynamicResource
ResourceKey={x:Static ToolBar.ButtonStyleKey}}"
Content="{Binding Content}"
Command="{Binding Command}" />
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
</UserControl>
我认为这是一种更优雅、更不容易出错的解决方案,而且我还没有谈论 Blendability 部分。请耐心点……这才是有趣的部分。
我在 XAML 中使用的 ImportExtension
标记扩展是我编写的一个自定义标记扩展,它有助于 UI 组合。通过 View-first 的方式,我们可以直接从 XAML 轻松创建和组合我们的 ViewModel,并使用 MEF。在这种情况下,我们只需要提供 ViewModel 的契约,也就是 Import 标记的单个参数,类型为 Type
,在本例中是:ICommandBarViewModel
。
我必须说,MEF 中的契约也可以是一个简单的 string
,但 string
容易出错,这也是我选择接口而不是 string
的原因之一。其他原因包括单元测试和 Blendability。
这里的简单技巧是在 Import 扩展中使用 MEF 来组合给定的类型。
通过 MEF 组合我们的 ViewModel,我们解决了在开发阶段需要处理的许多问题。
- 从 XAML 和代码中创建具有所有依赖项的 ViewModel。
- 在设计时创建一个虚拟 ViewModel,但在运行时创建一个真实的 ViewModel。
- 创建用于单元测试的 ViewModel 存根/模拟。
- 解析服务等依赖项,而不必关心对象的生命周期和来源。
既然我们(无论同意与否)都认为 MEF 在我们的 MVVM 模式中确实很酷,那么让我们来谈谈由 MEF 创建的 ViewModel 的 Blendability。
首先,让我们提一下 VS2010 中添加的 d:DataContext
XAML 属性,它也存在于 Blend 3 和 4 中。我在我之前的帖子中谈到过它:Blendability 第一部分 – 设计时 ViewModel。
d:DataContext
XAML 属性确实很有帮助,但真的有必要有两个数据上下文的定义,一个用于运行时,一个用于设计时吗?
就个人而言,我更喜欢只有一个数据上下文的定义,它将同时服务于运行时和设计时。这里我们回到老派,只使用 DataContext
属性,方法如下:
<UserControl x:Class="Blendability.Solution.Parts.CommandBarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ts="http://blogs.microsoft.co.il/blogs/tomershamam"
DataContext="{ts:Import ts:ICommandBarViewModel, IsDesigntimeSupported=True}">
...
</UserControl>
正如您所见,我使用相同的 Import 标记扩展来获得 ICommandBarViewModel
的两个不同实现。一个实现用于运行时,另一个实现用于设计时。运行时实现将在运行时由 Import 标记扩展组合并返回,而设计时实现将在设计时由 Import 标记扩展组合并返回。
您可能会问:
DataContext
在设计时是如何工作的?- 如果我想使用华丽的 Blend 示例数据怎么办?
ICommandBarViewModel
的设计时实现是什么,从哪里来?
这些问题的答案是:
- 与您直接从 XAML 创建 ViewModel 并设置
DataContext
属性的方式相同。VS 和 Blend 都支持它。 - 您可以随时移除
IsDesigntimeSupported=True
并继续使用d:DataContext
,或者您可以向 Import 标记扩展添加另一个属性,即设计时要使用的对象,并使用常规的d:SampleData
来获取它。 - 这里已经太晚了,所以我将在下一篇文章中提供这个问题的答案+代码。
接下来,我将揭示 MEF 的设计时支持,并提供 Import 标记扩展代码和其余部分。敬请关注!