65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2011年1月20日

CPOL

4分钟阅读

viewsIcon

16532

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,我们解决了在开发阶段需要处理的许多问题。

  1. 从 XAML 和代码中创建具有所有依赖项的 ViewModel。
  2. 在设计时创建一个虚拟 ViewModel,但在运行时创建一个真实的 ViewModel。
  3. 创建用于单元测试的 ViewModel 存根/模拟。
  4. 解析服务等依赖项,而不必关心对象的生命周期和来源。

既然我们(无论同意与否)都认为 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 标记扩展组合并返回。

您可能会问:

  1. DataContext 在设计时是如何工作的?
  2. 如果我想使用华丽的 Blend 示例数据怎么办?
  3. ICommandBarViewModel 的设计时实现是什么,从哪里来?

这些问题的答案是:

  1. 与您直接从 XAML 创建 ViewModel 并设置 DataContext 属性的方式相同。VS 和 Blend 都支持它。
  2. 您可以随时移除 IsDesigntimeSupported=True 并继续使用 d:DataContext ,或者您可以向 Import 标记扩展添加另一个属性,即设计时要使用的对象,并使用常规的 d:SampleData 来获取它。
  3. 这里已经太晚了,所以我将在下一篇文章中提供这个问题的答案+代码。

接下来,我将揭示 MEF 的设计时支持,并提供 Import 标记扩展代码和其余部分。敬请关注!

© . All rights reserved.