·目标
完成本课程预计时间为:25分钟
Composite Web Application Block的服务可以认为是以松耦合方式为其他组件提供特殊功能应用的类(服务与组件可以编译在相同或不同模块中),例如,某个服务可以提供安全认证,日志写入,或者硬件通讯的功能。工厂也提供了一些基本的服务供我们在程序中使用。当然,我们也可以根据我们的需要来为程序编写特殊的服务。
在本练习中,我们将学习如何去创建和注册一个供模块使用的服务,该服务将被自动注入到Object Builder中,我们将实现一个简单的模拟电子汇款服务(EFT)。
·准备
在课程开始之前,确定已将WCSF及其需要的组件安装完全。
打开之前课程完成的解决方案,并确定在VS中可使用WCSF向导包。
具体步骤如下
1. 使用VS打开解决方案。
2. 打开Tools菜单,单击Guidance Package Manager。
3. 在弹出的Guidance Package Manager对话框,点击Enable / Disable Packages按钮。
4. 在Enable and Disable Packages对话框中,将Web Client Development复选框选中。
5. 点击OK按钮。
·课程内容
本课程包含了以下任务:
· 任务1:创建服务所需的类(例如实体类等)
· 任务2:创建服务接口
· 任务3:创建接口实现类
· 任务4:熟悉依赖注入模式
· 任务5:注册服务
· 任务6:使用服务
接下来将逐一讲解这些任务。
任务1:创建服务所需的类
在本任务中,我们将创建一些为示例服务所用的应用类。
创建应用类
1. 在解决方案管理器中,找到EFT工程,并为其创建一个名为BusinessEntities的目录。
2. 在BusinessEntities目录上点击右键,选择Add,添加一个名为Transfer.cs新的代码文件到BusinessEntities目录下。
3. 按如下代码编写Transfer.cs文件。
using System;
namespace GlobalBank.EFT.BusinessEntities
{
public class Transfer
{
private string _sourceAccount;
private string _targetAccount;
private decimal _amount;
private string _status;
private Guid _id;
public Transfer()
{
}
public Transfer(string sourceAccount, string targetAccount, decimal amount, string status)
{
_sourceAccount = sourceAccount;
_targetAccount = targetAccount;
_amount = amount;
_status = status;
}
public Guid Id
{
get { return _id; }
set { _id = value; }
}
public string Status
{
get { return _status; }
set { _status = value; }
}
public decimal Amount
{
get { return _amount; }
set { _amount = value; }
}
public string TargetAccount
{
get { return _targetAccount; }
set { _targetAccount = value; }
}
public string SourceAccount
{
get { return _sourceAccount; }
set { _sourceAccount = value; }
}
}
}
任务2:创建服务接口
在本任务中,我们将为服务定义一个接口,该接口描述了两个银行账户间的电子汇款。
To create the service interface 创建服务接口
1. 在EFT工程中创建一个名为Interface的目录。
2. 在Interface目录上单击鼠标右键,选择Add-> New Item,将弹出Add New Item对话框,选择Interface模板,输入文件名ITransferService后,点击Add按钮,名为ITransferService.cs的接口定义文件将被创建。
3. 将以下代码复制粘贴到该接口文件中。
using GlobalBank.EFT.BusinessEntities;
using System;
namespace GlobalBank.EFT.Interface
{
public interface ITransferService
{
Transfer[] ProcessTransfers(Transfer[] transfers);
DateTime GetLastTransferDate();
}
}
任务3:添加服务实现类
在本任务中,我们将为EFT服务创建一个实现类。
创建ETF服务实现类
1. 在EFT工程中创建一个名为TransferService的类。
2. 为该类添加如下引用。
using GlobalBank.EFT.Interface;
using GlobalBank.EFT.BusinessEntities;
3. 继承上述接口,并使用如下代码来实现接口方法。
public class TransferService : ITransferService
{
private static DateTime _lastTransferDate = DateTime.MinValue;
public Transfer[] ProcessTransfers(Transfer[] transfers)
{
foreach (Transfer transfer in transfers)
{
transfer.Status = "Completed";
}
_lastTransferDate = DateTime.Now;
return transfers;
}
public DateTime GetLastTransferDate()
{
return _lastTransferDate;
}
}
完成上述任务后,你的解决方案文件结构将与图1类似。
图 1
方案浏览器中的EFT服务
任务4:熟悉依赖注入模式
依赖注入模式使得某个类与其依赖的对象可以松耦合方式更好的协同。使用依赖注入模式,我们将不需要在定义类的时候明确指明其所依赖的对象。与之替代的是,在一个类中,可以很方便的从外部资源(例如程序集)中去获得其所需的对象。通过WCSF,我们只需要在类中定义一个接口变量,而不关心它的具体实现。这意味着,我们可以很轻易地去替换这个接口的实现而不用改变该类的代码。这在对类进行隔离测试的时候也是很有用的。Composite Web Application Block库包含了一套基于Object Builder的依赖注入框架供我们使用;这个框架在工厂程序中将随处可见。
如果想获取更多关于依赖注入模式的信息,可参考WCSF帮助中“Dependency Injection”章节的内容。
任务5:注册服务
在本任务中,我们将注册之前任务实现的EFT服务。当然我们需要在其他模块或组件使用服务之前完成注册。我们可以将这个服务注册为仅当前模块(module service)可用或者全局可用(global service)。在本任务中,我们把EFT服务注册为模块级。
要注册模块服务,我们需要在模块初始化类的AddModuleServices方法中添加代码。
模块初始化类实现了IModuleInitializer接口。该类包含了当Composite Web Application Block加载当前模块的初始化代码。IModuleInitializer接口定义了两个方法,Load和Configure。我们可以在这两个方法中完成以完成模块的初始化,例如服务注册,添加站点地图节点等,如果想获得更多关于模块初始化以及IModuleInitializer接口的信息,可参考WCSF帮助中“Modules”章节的内容。
注册服务
1. 打开在EFT工程的EFTModuleInitializer.cs文件,我们就可以修改模块加载时的逻辑代码。
2. 为该类添加如下引用。
using GlobalBank.EFT.Interface;
using GlobalBank.EFT.Services;
3. 在AddModuleServices方法中,添加如下代码。
moduleServices.AddNew<TransferService, ITransferService>();
注意:参数moduleServices是当前模块所有服务的集合,在我们使用AddNew方法的使用,Object Builder会根据实现类的定义创建一个服务实例并添加到该模块服务集合中。在程序运行的任何时候,该服务都可以被注入到该模块中。
使用AddNew方法的时候,我们可以指定待注册的服务具体实现类型。该类型只是在OB实例化服务的时候被使用,我们只是通过接口定义来使用服务,而不会去关心该服务的具体实现方式。这样,我们可以很轻易的在代码中去替换接口的实现类,尤其是在做单体测试的时候,我们可以很轻松的使用一个虚拟的实现类来代替服务的真实实现类。
如果想获得更多关于服务注册的信息,可参考WCSF帮助中“How to: Register and Use Services”章节的内容。
任务6:使用服务
在本任务中,我们将通过以来注入的方式来获得EFT服务。
使用服务
1. 打开EFT模块控制类的代码文件(EFTController.cs)。
2. 为该类添加如下引用。
using Microsoft.Practices.ObjectBuilder;
using GlobalBank.EFT.Interface;
3. 声明一个服务接口的成员变量。
private ITransferService _transferService;
4. 重载一个带ITransferService参数的构造函数,并使用InjectionConstructor属性进行修饰,这样当OB创建一个EFTController实例的时候会优先使用该构造函数。
[InjectionConstructor]
public EFTController
([ServiceDependency] ITransferService transferService)
{
_transferService = transferService;
}
5. 实现一个用来获取上一次交易日期的方法,如下代码所示。
public virtual DateTime GetLastTransferDate()
{
return _transferService.GetLastTransferDate();
}
注意到在该控制类的定义中,我们没有引用任何与ITransferService接口实现相关的代码。这是因为,在任务4中,我们已经通过AddNew<TService, TRegisterAs>()方法注册了该服务。由于在构造函数的参数中使用ServiceDependency属性,OB在创建控制类的实例的时候,会自动去注入之前注册的服务。综上,我们将以最小的代码修改使得程序可以使用不同的服务实现类。
·验证
在本节,我们将创建一个单体测试用例来验证控制器(controller)是否能正确地使用EFT服务。
验证包含以下任务:
· 任务1:创建一个模拟服务
· 任务2:添加单体测试用例
以下章节将具体描述这两个任务。
任务1:创建模拟服务
在单体测试中,可以使用一些较简单的模拟对象来模仿复杂真实对象的行为和表现。特别是但真实对象不容易集成到单体测试中时,这些模拟对象就显得非常有用。在本任务中,我们将创建一个模拟EFT服务的对象,来对EFTController类进行独立测试。
创建一个模拟服务
1. 往EFT.Tests工程的Mocks目录中添加一个名为MockTransferService的类。
2. 在MockTransferService类中添加如下引用。
using GlobalBank.EFT.Interface;
using GlobalBank.EFT.BusinessEntities;
3. 编写类代码如下。
public class MockTransferService : ITransferService
{
public DateTime LastTransferDate = new DateTime(2000, 1, 1);
public DateTime GetLastTransferDate()
{
return LastTransferDate;
}
public Transfer[] ProcessTransfers(Transfer[] transfers)
{
return transfers;
}
}
任务2:添加一个单体测试
在本任务中,我们将添加一个单体测试用例验证EFTController是否能正确使用EFT服务。
添加单体测试用例
1. 打开EFTModuleControllerFixture类文件。
2. 在该文件中添加如下引用。
using Microsoft.Practices.ObjectBuilder;
using Microsoft.Practices.ObjectBuilder.WCSFExtensions;
using GlobalBank.EFT.Tests.Mocks;
using GlobalBank.EFT;
using GlobalBank.EFT.Interface;
3. 添加如下测试方法。
[TestMethod]
public void GetLastTransferDateConsumesITransferService()
{
MockTransferService service = new MockTransferService();
EFTController controller = new EFTController(service);
DateTime transferDate = controller.GetLastTransferDate();
Assert.AreEqual<DateTime>(service.LastTransferDate, transferDate);
}
测试程序使用一个虚拟的EFT服务创建了一个EFTController实例。并调用控制器对象的GetLastTransferDate方法。验证其返回值是否与虚拟EFT服务的返回值相同。
4. 使用测试管理器运行测试用例。
a. 在方案管理器中,点击EFT.Tests工程。
b. 在Test菜单中,点击Start Selected Tests Project without Debugger(或者使用组合键CTRL+SHIFT+X)。
单体测试结果将显示在测试结果窗口。
稍后将更新本课程示例代码。
在国内,能找到的WCSF的中文资料少之又少,如果这个译文确实对大家认识和使用WCSF有所帮助,笔者将努力将该系列教程全部翻译,希望能够得到大家的支持。
如果转载,请注明出处,谢谢^^