WCF 示例 – 第八章 – WPF 客户端 – 继电器命令






4.96/5 (15投票s)
WPF ICommand 实现,基于 Josh Smith 的模式。
第七章 | 第九章 |
系列文章
WCF by example 是一系列文章,描述了如何使用 WCF 进行通信和 NHibernate 进行持久化来设计和开发 WPF 客户端。该系列介绍描述了文章的范围,并从高层次讨论了架构解决方案。
章节概述
在上一章中,我们继续在客户端进行工作,并提出了一种机制,允许以解耦的方式从 ViewModel 类调用服务器服务。在本章中,我们将定义 View (XAML) 从 ViewModel 调用上述服务的模式。我们将介绍 RelayCommand 模式,它利用了 XAML 绑定功能,简化了 ViewModel 中声明方法的执行。
此模式基于 Josh Smith 关于 MVVM 模式的一篇文章;他在他的博客中涵盖了许多 MVVM 主题。本章的源代码可以在CodePlex changeset 93453中找到。eDirectory 解决方案的最新代码可以在CodePlex中找到。
ICommand
Josh 的文章中可以找到对该模式的全面解释。因此,我们将专注于在我们的解决方案中实现它,而不是详细解释该模式。您可能认为在继续本章之前阅读这篇文章很有益。
在我们的客户端中,我们在第六章定义 XAML 时创建了两个命令按钮;保存和刷新按钮。当时可能并不明显,但我们声明了对 ViewModel 中操作的绑定。在第六章的 `CustomerViewModel` 中没有定义这些方法,但并没有阻止我们执行应用程序。值得注意的是 XAML View 的这个方面,错误的绑定不会抛出异常;但是,在调试应用程序时,可以在 VS 中找到明确记录的警告。例如,第六章中保存按钮的 XAML 声明是
<Button Grid.Column="1" Grid.Row="2"
Padding="5" Margin="5"
Command="{Binding SaveCommand}">Save</Button>
命令操作将调用 ViewModel 中一个名为 `SaveCommand` 的只读属性;ViewModel 中的属性必须实现 `ICommand` 接口才能工作。
继电器命令
`RelayCommand` 类提供了一种简单的机制,用于在 ViewModel 中声明属性,以便按钮可以使用绑定定义调用操作。以保存按钮为例,绑定指示按钮将调用一个名为 `SaveCommand` 的属性;`CustomerViewModel` 类将该属性声明如下
01 private RelayCommand SaveCommandInstance;
public RelayCommand SaveCommand
{
get
{
if (SaveCommandInstance != null) return SaveCommandInstance;
02 SaveCommandInstance = new RelayCommand(a => Save());
return SaveCommandInstance;
}
}
private void Save()
{
03 var result = CustomerServiceAdapter.Execute(
s => s.CreateNewCustomer(Model.NewCustomerOperation));
Refresh();
}
我们使用惰性方法(第 02 行)来实例化 `RelayCommand` 私有实例(第 01 行);这种方法允许使用 lambda 表达式来指示 View 调用 `RelayCommand` 的 `Execute` 方法时将调用的内部方法。值得注意的是,`RelayCommand` 有两个构造函数可用;在本例中,我们使用的是只需要传递要调用的操作的构造函数。有一个重载构造函数提供了一种验证机制,在这种情况下,对于命令按钮,它确保只有在验证成功时才启用按钮。我们可能会在后续章节中看到如何使用此功能的示例。
让我们看看还有哪些其他更改需要进行才能使我们的前端调用服务方法。
服务适配器 - 在 ViewModel 中的使用
在第 03 行,我们可以看到 `Save` 方法正在调用 `CreateNewCustomer` 服务方法。此时有几个方面需要解释,包括 `CustomerServiceInstance` 的声明方式以及使用 `NewCustomerOperation` DTO 将客户详细信息从 UI 传递到服务的机制。
`CustomerServiceAdapter` 在 ViewModel 构造函数中实例化
01 private readonly ServiceAdapter<ICustomerService> CustomerServiceAdapter;
public CustomerViewModel()
{
02 CustomerServiceAdapter = new ServiceAdapter<ICustomerService>();
Refresh();
View = new CustomerView { DataContext = this };
View.ShowDialog();
}
在第 01 行,我们实例化 `ServiceAdapter`,指示我们希望在此 ViewModel 中处理 Customer 服务。这是在客户端利用 WCF 服务的一种非常好的方式;ViewModel 不与 WCF 服务实现耦合,这在开发和测试时降低了复杂性。如果您同时开发服务器端和客户端,它还提供了一种避免使用 MS WCF 代理的便捷方法。我们在第 XII 章 - WCF 实现中讨论了使用此设计在客户端实现 WCF 服务有多么容易。
我们在显示视图之前调用 `Refresh` 方法;我们这样做是为了初始化 Model
private void Refresh()
{
01 var result = CustomerServiceAdapter.Execute(s => s.FindAll());
02 Model = new CustomerModel { NewCustomerOperation = new CustomerDto(),
CustomerList = result.Customers};
}
`Refresh` 方法的目的是通过调用 `FindAll` 服务方法来检索所有 `Customer` 实例,然后创建 `CustomerModel` 类的实例,该类是 `CustomerView` 的模型。在第 02 行,一个 `CustomerDto` 实例被赋值给 `NewCustomerOperation` 属性。该属性使用 `TwoWay` 模式绑定到前端的客户详细信息部分。这意味着 XAML 能够更新 `CustomerDto` 实例,而无需任何其他额外代码;这还不错。正如我们在 `Save` 方法中所见,这是一种填充服务方法参数的好模式。
引导程序更改
|
在此项目阶段,我们只需要 WPF 客户端在单个域进程中调用服务方法,而无需处理 WCF 服务的开销。在后续章节中,我们将介绍 DI 以及如何在不创建对服务器组件的客户端引用的情况下部署服务器程序集。目前,为了保持简单,我们将仅将这些引用添加到客户端项目中。 |
然后,我们只需要增强 `eDirectoryBootStrapper`,将以下代码添加到 `InitialiseDependencies` 方法中
private void InitialiseDependencies()
{
01 GlobalContext.Instance().TransFactory = new TransManagerEntityStoreFactory();
02 Container.RequestContext = new RequestContextNaive();
03 ClientServiceLocator.Instance().CommandDispatcher = new DirectCommandDispatcher();
}
因此,我们指示客户端我们希望在 `GlobalContext` 服务中使用 `TransFactory` 的内存实现(第 01 行),并且 `RequestContext` 设置为朴素实现(第 02 行):`RequestContextNaive` 类。我们还设置了 `CommandDispatcher`,以便使用我们在上一篇文章中讨论的直接服务实现(第 03 行)。
差不多了
如果我们运行应用程序,输入一些客户详细信息,然后按“保存”按钮,我们会注意到什么都没有发生。但是,如果我们调试代码,我们可以确认我们的 `Save` 方法正在执行。
似乎一切正常;在执行 `CreateNewCustomer` 服务方法后,`Refresh` 方法似乎工作正常。我们甚至可以检查结果,看到返回的集合包含一个客户,并且所有属性都已正确填充。那么为什么前端没有刷新呢?
这是 WPF 客户端应用程序的另一个特点;我们需要向 View 指示 Model 已刷新。为了做到这一点,我们需要引入 `INotifyPropertyChanged` 接口,我们在下一篇文章中讨论了这一点。
章节总结
在本章中,我们介绍了 `RelayCommand` 类,它是 Josh Smith 实现的 `ICommand`。我们讨论了 View 和 ViewModel 的设计方式,以便前端可以使用 XAML 绑定调用服务方法。此时,我们的应用程序几乎已经可以工作了,使用的是内存模式,并在客户端域进程中调用服务器方法;为了方便起见,我们目前正在避免使用 WCF 和 NHibernate 组件。此时,我们只需要 ViewModel 在 Model 更新时通知 View。
下一章将解决通知问题,并提供一个全面的基础设施来开始向客户展示我们的解决方案功能。