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

WCF 示例 – 第七章 – WCF 服务与 ViewModel 的解耦

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2010年9月4日

CPOL

7分钟阅读

viewsIcon

72456

基础 WCF 分发层 - WCF 服务与 ViewModels 的解耦。

Previous

Next

第六章 第八章

系列文章

WCF 示例是一系列文章,介绍了如何使用 WCF 进行通信和 NHibernate 进行持久化来设计和开发 WPF 客户端。该系列介绍描述了文章的范围,并从高层次讨论了架构解决方案。

章节概述

在上一章中,我们介绍了基本的 WPF 客户端。目前,视图 (View) 和模型 (Model) 几乎准备就绪,但 ViewModel 并没有提供调用服务方法的任何机制。我们之前讨论过将客户端与 WCF 分发层解耦的必要性。目标是提供一个支持快速应用程序开发 (RAD) 的开发环境,该环境能够利用业务探索,而无需部署完整生产基础设施的开销。我们希望提供一个更改成本低、部署简单的开发环境。我们不想在 IIS 中安装服务,也不想维护 SQL Server 数据库;我们正在寻找一个包含一堆文件的安装包,如果需要,甚至可以部署在内存条上。业务分析师、产品负责人、测试人员都应该能够以简单的方式执行此应用程序。

因此,我们需要抽象分发层,以便可以以透明的方式使用不同的实现。 在本章中,我们将描述契约定位器 (contract locator) 以及客户端如何使用此机制来建立一种管道模式 (pipeline pattern),从而提供我们正在寻找的所需功能。

原始实现虽然解决了客户端与服务之间的耦合问题,但需要为每种服务实现维护一组客户端类,这增加了开销。一个基于泛型类、泛型函数和匿名方法的 `ServiceAdapter` 提供了更好的实现,客户端无需实现任何额外的类来解耦服务调用。我们还在本文中引入了 `CommandDispatcher`,这在稍后将 WCF 服务引入客户端时是必需的,有关更多详细信息,请参见第 WCF 示例 - 第 XII 章 - WCF 实现 章。

本章的源代码可以在 CodePlex 更改集 93405 中找到。 eDirectory 解决方案的最新代码可以在 CodePlex 中找到。

客户端重构

对于熟悉本系列并可能对重构范围感兴趣的读者。以下屏幕截图显示了添加、删除和修改的文件

为此次重构采取了以下措施:

  • 移除 WPF 项目中的 `IClientServices`
  • 移除 `IContractLocator`
    • ServerContractLocator 已移除 -- 需要重构测试:CustomerServiceTests
    • 从 `ClientServiceLocator` 中移除 -- 需要对 `ICommandDispatcher` 进行引用
    • 移除 `ClientContractLocator`
    • 移除 `IContractLocator`
    • 移除 WPF 项目中的 `CustomerServiceAdapter`
    • 移除 WPF 项目中的 `ServiceAdapterBase` 类
  • 添加 `eDirectory.Common.Message`, `ICommandDispatcher`
  • 将 `CommandDispatcher` 添加到 `ClientServiceLocator`
  • 创建服务器 `DirectCommandDispatcher` 类
  • 创建泛型 `ServiceAdapter` 类 -- 此版本不包含异步内部调度器
  • 重构测试

命令调度器 (Command Dispatcher)

`ICommandDispatcher` 接口提供了一种机制,允许客户端在调用给定服务时指示使用哪个方法和参数。客户端的唯一要求是知道契约接口,它不需要知道实现的细节。另一方面,`ICommandDispatcher` 的职责是处理服务实现并将方法结果传递给客户端。该接口是

public interface ICommandDispatcher
{
    TResult ExecuteCommand<TService, TResult>(Func<;TService, TResult> command)
        where TResult : IDtoResponseEnvelop
        where TService : class, IContract;
}

在本章中,我们将仅讨论 `DirectCommandDispather` 实现,我们使用它将客户端直接连接到服务后端,而无需 WCF 和在 IIS 上部署应用程序。在此模式下,应用程序仅运行在单个应用程序域中。这对于测试和 RAD 非常有用。实现非常简单

public class DirectCommandDispatcher
    : ICommandDispatcher
{
    #region Implementation of ICommandDispatcher<TService>

    public TResult ExecuteCommand<TService, TResult>(Func<TService, TResult> command)
        where TResult : IDtoResponseEnvelop
        where TService : class, IContract
    {
01      var service = GetService<TService>();
        return command.Invoke(service);
    }

    #endregion

02  private readonly IDictionary<Type, Type> _serviceMap = new Dictionary<Type, Type>
                            {
                                {typeof(ICustomerService), typeof(CustomerService)}
                            };

    private TService GetService<TService>() where TService : class, IContract
    {
        var type = typeof(TService);
        if (!_serviceMap.ContainsKey(type))
        {
            var msg = "Implementation for contract: {0} was " + 
                      "not defined in the dispatcher service map";
            msg = string.Format(msg, type.Name);
            throw new NotImplementedException(msg);
        }
03      return (TService)Activator.CreateInstance(_serviceMap[type]);
    }
}

实现只是一个工厂类(第 01 行),它根据客户端传入的 `TService` 创建服务实例(第 03 行)。如果在服务器端添加了新契约,需要记住的是在第 02 行中添加服务到映射表中。

在第 XII 章 - WCF 实现 中,我们将讨论 `CommandDispatcher` 的 WCF 实现。这是生产实现,在某种程度上比我们上面讨论的更简单,因为它在向解决方案添加契约时不需要任何修改。

客户端服务定位器 (Client Service Locator)

对于某些客户端服务,在整个客户端应用程序的生命周期内使用单个实例就足够了,也是推荐的。我通常喜欢创建一个服务容器,代码可以轻松从中获取这类资源,而无需暴露 DI 容器。有几种方法可以提供此功能,并且人们有不同的意见。在这种情况下,我们使用定位器模式与 DI 结合,以在运行时动态解析服务实现。有关 DI 的更多信息,请参阅第 X 章 - 使用 Spring.Net 进行依赖注入

`ClientServiceLocator` 的实现非常直接;目前,我们只公开 `ICommandDispacher` 服务,但在本系列结束时,这个定位器将公开另外两个服务。

public class ClientServiceLocator
{
    static readonly Object LocatorLock = new object();
    private static ClientServiceLocator InternalInstance;

    private ClientServiceLocator() { }

    public static ClientServiceLocator Instance()
    {
        if (InternalInstance == null)
        {
            lock (LocatorLock)
            {
                // in case of a race scenario ... check again
                if (InternalInstance == null)
                {
                    InternalInstance = new ClientServiceLocator();
                }
            }
        }
        return InternalInstance;
    }

    public ICommandDispatcher CommandDispatcher { get; set; }
}

服务适配器 (Service Adapter)

当 ViewModels 想要调用服务方法时,它们会使用 `ServiceAdapter`。这是一个泛型类,因此 ViewModel 在创建实例时也会确定 `ServiceAdapter` 将处理哪个服务契约,例如

01  var customerServiceAdapter = new ServiceAdapter<ICustomerService>();
    var dto = new CustomerDto
                            {
                                FirstName = "Joe",
                                LastName = "Bloggs",
                                Telephone = "9999-8888"
                            };

02  var customerInstance = 
        customerServiceAdapter.Execute(s => s.CreateNewCustomer(dto));

在第 01 行,为 `ServiceAdapter` 创建了一个新实例,并传入了 `ICustomerService` 契约接口。此机制将有助于在第 02 行调用服务方法。要执行该方法,我们只需使用 `Execute` 方法,这要求我们传递一个函数表达式,该表达式接受服务实例(这是 `CommandDispatcher` 的职责)并执行其方法之一。返回值始终是 `IDtoResponseEnvelop` 的实例,它是服务方法的結果。

我们构建 `ServiceAdapter` 和 `CommandDispatcher` 的方式为开发人员提供了一种强类型机制,供 ViewModels 调用方法和传递输入参数。它类似于我们将在本系列稍后看到的 `RepositoryLocator`,但泛型 `ServiceAdapter` 提供了处理不同服务契约的附加功能。值得注意的是,`ServiceAdapter` 不需要服务实现,而且它不负责创建/维护服务实例,`CommandDispatcher` 负责这方面。该类的主要目的是以通用的方式管理业务警告/消息。实现如下:

public class ServiceAdapter<TService>
    where TService:class, IContract
{
    public TResponse Execute<TResponse>(Func<TService, TResponse> command)
        where TResponse : IDtoResponseEnvelop
    {
01          var dispatcher = ClientServiceLocator.Instance().CommandDispatcher;
02          var result = dispatcher.ExecuteCommand(command);
        if (result.Response.HasWarning)
        {
03              // TODO: Implement Business Warning processing -- See Chapter XIV

        }
        if (result.Response.HasException)
        {
04              // TODO: Implement Business Exception processing -- See Chapter XIV
        }
        return result;
    }
}

在第 01 行,我们使用新的 `ClientServiceLocator` 来获取 `CommandDispatcher`,然后在第 02 行,命令被传递给调度器;请注意 `ServiceAdapter` 完全不知道正在使用哪个服务实现。

这不是该类的最终版本;在第 XIV 章 - 验证和异常处理 中,我们将在此类中讨论业务警告和异常的实现,第 03 行和第 04 行。另外,在第 XII 章 - WCF 实现 中,介绍了调用服务的更好异步机制。

测试重构

我们已经有一套测试来从服务器端执行我们的服务。这些测试已被重构为使用 `ServiceAdapter`。这样,我们就可以测试新基础设施。我将代码留给读者自行检查,看看这些测试是如何实现的,它们提供了调用 `ServiceAdapter` 的全面示例。

章节总结

在本章中,我们为客户端端的分发层设置了基线。我们的设计提供了 ViewModel 类与我们在本章开头提到的服务之间所需的解耦。通过对服务测试进行一些小的更改,我们还演示了客户端如何轻松地调用服务器端服务。这种模型对于提供可插拔的架构至关重要,使我们能够在业务探索阶段部署我们的应用程序,而无需使用 WCF 来简化此时的部署过程。

我们尚未讨论 WCF 实现;这方面将在本系列的后续章节中介绍。然而,正如我们将看到的,一旦我们有了本章介绍的模式,WCF 实现相对简单。请参阅第 XII 章 - WCF 实现

下一章 中,我们将看到命令如何在前端实现,以及 ViewModel 类如何使用本章提到的 `ServiceAdapter` 调用服务。我们离拥有一个全面的客户端和服务器端基础设施已经非常近了,可以开始向我们的客户部署解决方案的版本了。

© . All rights reserved.