WCF:正确的方式。快速参考指南
Miguel Castro 开发的手动创建和使用 WCF 服务的开发人员工作流程
引言
Miguel Castro(以下简称 MC)提出了一个有说服力的论点,认为 WCF 服务应用程序的包含项目模板“产生了过多的负担”,并且不利于“最佳实践”。他进一步认为,添加服务引用也“差强人意”。为了更好地说明,他指出 WCF“可能是微软推出的最酷的技术之一”。
WCF 模板方法没有分离契约,将服务紧密耦合到 Web 服务(尽管可以使用其他传输方式),Web 服务 webconfig 包含过多元数据,并且服务引用会重复契约。
MC 的批评总结可以在这里找到。使这篇文章成为一个建设性练习的是,他还概述了一种手动创建和使用 WCF 服务的方法:这种方法是松耦合的,遵循良好的设计标准,最重要的是,易于维护。本文将他的过程分解为一个简洁的开发人员工作流,分为不同的操作和步骤——一个快速参考指南。
|
图 1:MC 建议不要使用模板“WCF 服务应用程序”
|
|
图 2:MC 建议不要使用“添加服务引用”功能来引用 WCF 服务。
|
约定
- 在项目中,配置文件(Web.config 和 App.config)中任何非默认的条目都会被添加到名为 Configuration 的文件夹中,并且会在配置文件中添加
configSource
链接。 - WPF 客户端仅用于演示目的。任何可以进行 WCF 服务调用的客户端都可以使用。
- 演示代码中提供了 Web 服务和 TCP 主机。也可以使用其他传输方式。文章中分解了 Web 服务。
- 以下步骤假设已创建新解决方案,并且 .NET Framework 3+ 可用于开发。
设置项目
解决方案被分成以下项目(展示了分离的功能和好处)
- 模型
- 功能 - 业务对象定义
- 好处 - 典型分离;在不同程序集之间重用
- 契约
- 功能 - 服务和数据契约
- 好处 - 在服务和服务的消费者之间重用(面向 SOA 原则)
- Service
- 功能 - 一个或多个;包含基于契约的服务方法
- 好处 - 在其他解决方案中重用;对象模型的过程式入口点
- 服务主机
- 功能 - 由代理引用;提供到服务的传输
- 好处 - 允许多种传输方式(通过其他服务主机)使用服务
- 客户端代理
- 功能 - 消费者访问服务实现的入口点
- 好处 - 在多个客户端之间重用;可以在不同服务主机上使用不同的终结点名称进行重用
- 客户端
- 功能 - 服务客户端或消费者
- 好处 - 典型分离;可以使用各种传输方式访问服务
|
|
图 3:与服务相关的项目
|
图 4:与传输相关的项目
|
|
|
图 5:用户界面项目
|
此任务可分为四类
- 设置服务层
- 添加“模型”项目
- 创建新项目 > 类库 > 名称以“Model”结尾
- 添加对
System.Runtime.Serialization
的引用 - 为打算使用的每个业务对象添加一个类
- 在每个类中添加
using
子句以引用System.Runtime.Serialization
- 将
DataContractAttribute
添加到类 - 将
DataMemberAttribute
添加到类的public
成员
- 添加“
Contract
”项目- 添加新项目 > 类库 > 名称以“
Contract
”结尾 - 为打算使用的每个服务类添加接口
- 在每个接口中,添加对
System.ServiceModel
的引用 - 将
ServiceContractAttribute
添加到接口。 - 将
OperationContractAttribute
添加到接口的成员。
- 添加新项目 > 类库 > 名称以“
- 添加“
Service
”项目- 添加新项目 > 类库 > 名称以“
Service
”结尾 - 添加对 Model 和 Contract 项目的引用
- 为您已添加到“
Contract
”项目中的每个接口添加一个类 - 在每个类中,添加
using
子句以引用 Model 和 Contract 项目 - 实现每个接口
- 添加新项目 > 类库 > 名称以“
- 添加“模型”项目
- 设置传输
- 对于 Web 服务
- 添加“Service Host”项目
- 添加新项目 > ASP.NET Web 应用程序 > 名称以“
WebService
”结尾 - 添加对 Service 项目的引用
- 删除 Default.aspx
- 为每个服务类添加一个文本文件,并将文件名扩展名更改为 SVC
- 将以下代码模板(插入服务类的命名空间)添加到文件中
<%@ ServiceHost Service ="namespace.of.service" %>
- 添加“Client Proxy”项目
- 添加新项目 > 类库 > 名称以“
ClientProxy
”结尾 - 添加对
System.ServiceModel
的引用 - 添加对
Contract
项目的引用 - 为
Contract
s 中的每个接口添加一个类 - 在每个类中
- 添加
using
子句以引用System.ServiceModel
- 实现接口
- 继承
ClientBase<TheInterface>
- 在每个方法中,通过基类的 Channel 属性引用服务方法
- 添加
- 添加新项目 > 类库 > 名称以“
- 对于 Web 服务
- 设置使用者
- 对于 WPF 客户端
- 添加新项目 > WPF 应用程序 > 名称以“
WpfUi
”结尾 - 添加对
System.ServiceModel
的引用 - 添加对
ClientProxy
,Model
和Contract
项目的引用 - 在
App.config
的configuration
/system.serviceModel
/client
元素中为服务类添加终结点 - 在进行服务调用的类中
- 添加
using
子句以引用System.ServiceModel
,ClientProxy
,Model
和Contract
项目
- 添加
- 调用服务
- 创建新的代理对象
- 调用代理的方法,该方法映射到服务方法调用
- 添加新项目 > WPF 应用程序 > 名称以“
- 对于 WPF 客户端
- 配置解决方案
- 设置启动项目
- 调试本地
TcpHost
项目- 在解决方案资源管理器中右键单击解决方案 > 属性
- 选择启动项目 > 多个启动项目
- 选择
TcpHost
和WpfUi
项目
- 调试本地
- 设置启动项目
常见维护任务
- 添加服务
- 为服务创建
Interface
- 在
Service
项目中的新类中实现interface
- 在
ClientProxy
中创建一个类,该类实现ClientBase
和引用该服务的新interface
- 在
WebService
中创建一个 SVC 文件(从文本文件重命名),该文件映射到服务命名空间 - 在 UI 项目中创建一个
endpoint
,该endpoint
指向 SVC 文件,并指定契约(接口) - 在
WebService
中创建一个指向Service
类的endpoint
- 使用
ClientProxy
类使用服务
- 为服务创建
- 添加服务方法
- 为方法创建
interface
定义 - 在
Service
项目中的Service
类中实现该方法 - 向
ClientProxy
类添加一个方法 - 使用
ClientProxy
类使用服务方法
- 为方法创建
关注点
ClientProxy
构造函数有一个参数 endpointConfigurationName
,该参数传递给 ClientBase
构造函数。这仅用于使服务调用能够从使用者路由到任一传输(TCP/HTTP)。在实际应用程序中,这种情况不太可能发生,可以将其删除。将为契约定义单个终结点。
结论
使用 WCF 这一酷炫技术并不意味着你必须放弃良好的设计原则。Miguel Castro 已经将其分解。本文总结了用于实现他的方法的开发人员工作流。
历史
- 2010-10-04 - 在“添加服务”中添加
WebService
终结点 - 2010-10-01 - 初始提交