本文将介绍如何在WPF客户端应用程序中使用Windows Communication Foundation (WCF) 进行通信,并使用NHibernate作为数据持久化工具。同时,将采用Model-View-ViewModel (MVVM) 设计模式来构建应用程序的架构。
RelayCommand模式基于Josh Smith关于MVVM模式的文章。将重点关注在解决方案中的实现,而不是详细解释模式本身。在本章的源代码可以在CodePlex变更集93453中找到。eDirectory解决方案的最新代码可以在CodePlex上找到。
在客户端中,在第六章定义了两个命令按钮,即保存和刷新按钮。当时可能不明显,但在XAML中声明了对ViewModel中动作的绑定。这些方法在第六章的CustomerViewModel中没有定义,但这并没有阻止执行应用程序。值得注意的是XAML视图的这一方面,错误的绑定不会抛出异常;然而,在调试应用程序时,VS中可以找到良好记录的警告。例如,第六章中保存按钮的XAML声明如下:
命令操作将调用ViewModel中的一个只读属性SaveCommand;ViewModel中的属性必须实现ICommand接口才能工作。
RelayCommand类提供了一个简单的机制,用于在ViewModel中声明属性,以便按钮可以使用绑定定义调用操作。在保存按钮的例子中,绑定指示按钮将调用一个名为SaveCommand的属性;CustomerViewModel类声明属性如下:
private RelayCommand SaveCommandInstance;
public RelayCommand SaveCommand
{
get
{
if (SaveCommandInstance != null)
return SaveCommandInstance;
SaveCommandInstance = new RelayCommand(a => Save());
return SaveCommandInstance;
}
}
private void Save()
{
var result = CustomerServiceAdapter.Execute(s => s.CreateNewCustomer(Model.NewCustomerOperation));
Refresh();
}
使用延迟实例化RelayCommand私有实例的方法(第2行),这种方法允许使用一个lambda表达式,指示当视图调用RelayCommand的Execute方法时将被调用的内部方法。值得注意的是,RelayCommand有两个构造函数可用;在这个例子中,使用的是只需要传递要调用的操作的构造函数。还有一个重载的构造函数提供了验证机制,在命令按钮的情况下,确保只有在验证成功时按钮才被启用。将在后续章节中看到如何使用这个特性的例子。
在第3行中,可以看到Save方法正在调用CreateNewCustomer服务方法。需要解释的两个方面是CustomerServiceInstance的声明方式,以及使用NewCustomerOperation DTO将客户详细信息从UI传递到服务的机制。
private readonly ServiceAdapter CustomerServiceAdapter;
public CustomerViewModel()
{
CustomerServiceAdapter = new ServiceAdapter();
Refresh();
View = new CustomerView { DataContext = this };
View.ShowDialog();
}
在第1行中,实例化了ServiceAdapter,表明想要处理这个ViewModel的Customer服务。这是在客户端利用WCF服务的一个非常好的方式;ViewModel没有与WCF服务实现耦合,这在开发和测试时减少了复杂性。它还提供了一个简单的方法来避免使用MS WCF代理,如果正在开发服务器和客户端。在第十二章 - WCF实现中讨论了如何使用这种设计在客户端实现WCF服务是多么容易。
刷新方法在显示视图之前被调用;这样做是为了初始化模型:
private void Refresh()
{
var result = CustomerServiceAdapter.Execute(s => s.FindAll());
Model = new CustomerModel { NewCustomerOperation = new CustomerDto(), CustomerList = result.Customers };
}
刷新方法的目的是调用FindAll服务方法来检索所有Customer实例,然后创建CustomerModel类的实例,这是CustomerView的模型。在第2行中,一个CustomerDto实例被分配给NewCustomerOperation属性。这与前端的客户详细信息部分绑定,使用双向模式。这意味着XAML能够在不需要任何其他额外代码的情况下更新CustomerDto实例;还不错。这是填充服务方法参数的一个好模式,就像在Save方法中看到的那样。
在项目的这个阶段,只需要WPF客户端在单个域进程中调用服务方法,而不需要处理WCF服务的开销。
private void InitialiseDependencies()
{
GlobalContext.Instance().TransFactory = new TransManagerEntityStoreFactory();
Container.RequestContext = new RequestContextNaive();
ClientServiceLocator.Instance().CommandDispatcher = new DirectCommandDispatcher();
}
指示客户端想要使用GlobalContext服务中的内存实现TransFactory(第1行),并将RequestContext设置为简单的实现(第2行):RequestContextNaive类。还设置了CommandDispatcher,以便使用直接服务实现(第3行),在之前的文章中讨论过。
如果运行应用程序并输入一些客户详细信息,然后按下保存按钮,注意到什么也没有发生。但如果调试代码,可以确认Save方法被执行了:
似乎一切都在正常工作;刷新方法在CreateNewCustomer服务方法执行后似乎工作得很好。
甚至可以检查结果,看到返回的集合包含一个客户,所有属性都正确填充。那么为什么前端没有被刷新呢?
这是WPF客户端应用程序的另一个特性;需要指示视图模型已经被刷新。为了做到这一点,需要引入INotifyPropertyChanged接口,在下一篇文章中介绍这个方面。
在本章中,介绍了RelayCommand类,这是Josh Smith实现的ICommand的一个实现。讨论了视图和ViewModel是如何设计的,以便前端可以使用XAML绑定调用服务方法。到目前为止,几乎已经让应用程序工作了,使用内存模式并在客户端域进程中调用服务器方法;目前避免了WCF和NHibernate组件,以方便起见。