第1章 wcf基础
什么是wcf: System.ServiceModel.dll
服务 服务的执行边界: proxy
地址:http/https,tcp,ipc,peer newwork,msmq,service bus(服务总线地址)
契约:服务契约,(类虽然可以使用内部的属性,索引器,静态成员,但WCF客户端却无法访问)
数据契约,错误契约,消息契约(不是WCF 的常见情况)
托管:iis托管只能使用http协议
was托管:(window激活服务)
wsa扩展程序(window server appfabric):主要面向的事wf服务
选择宿主:p21,22(互联网,局域网)
绑定:基本绑定,TCP绑定,IPC绑定,WEB服务绑定,MSMQ绑定
选择绑定:p24
终结点
元数据的交换:1.基于http-get的元数据,2.元数据交换终结点
客户端编程:生成代理
WCF体系结构: 不使用代理类,使用通道直接调用服务ChannelFactory
第2章 服务契约
操作重载:不同的name区分操作
契约继承
服务契约分解与设计:服务契约的最佳数量3-5个
第3章 数据契约
net格式器:1.BinaryFormatter 2.SoapFormatter
wcf格式器:DataContrct
第4章 实例管理
单调服务:percall 客户端持有一个代理的引用,不是实际的对象 (sessionmode.notallowed)
会话服务:persession (sessionmode.required)
单例服务:single 全局日志
限流
第5章 操作
请求应答操作,默认的操作模式
单向操作,没有返回值
回调操作(双向操作)
第6章 错误契约
错误契约,客户端捕捉
第7章 事务
第8章 并发管理
第9章 队列服务
第10章 安全
第11章 服务总线
中继服务
----------------------------------------------------------------------
WCF编码规范
一个全面完整的编码规范是成功交付产品的根本。规范有助于推行最佳实践以及避免缺陷,可以让团队成员更容易分享知识与技能。传统的编码规范是一本浩如烟海一般的高文大册,厚达数百页,详尽了每个指南的基本原理。制定这样的规范固然是有胜于无,然而,如此努力却很难被大多数开发者所接受。比较而言,本书介绍的WCF编码规范却只有薄薄的几篇,主要介绍了WCF编码细节、编码内容以及设计目的。我相信,若要充分理解各种编程决策,可能需要阅读大量书籍,积累数年的经验,然而如果要实施编码规范则不必如此。当吸收一名新兵加入你的团队时,你可以交给他这份规范,告诉他:“先看看这个。”若要完全理解以及认识到范围的价值,或许需要时间与经验,然而,如果只是需要在此之前就能够遵循规范的约定,却是一蹴而就的事情。编码规范详细介绍了编码要求、缺陷、指南以及建议。编码规范同时还使用了本书介绍最佳实践以及辅助类。
通用设计指南
- 所有的服务必须遵循以下原则:
- 服务是安全的。
- 服务操作在系统中应该保持状态一致。
- 服务是线程安全的,且可以被并发客户端访问。
- 服务是可靠的。
- 服务是健壮的。
- 服务应该遵循以下可选原则:
- 服务是可互操作的。
- 服务的规模是不变的。
- 服务是可用的。
- 服务是及时响应的。
- 服务是受限的,阻塞客户端的时间不能过长。
WCF基础
- 应该将服务代码放入到类库中,而不是放到宿主EXE中。
- 不要为服务类提供参数构造函数,除非托管的服务是明确的单例服务。
- 在相关的绑定中启用可靠性。
- 要为契约提供有意义的命名空间。对于公开向外的服务,可以使用公司的URL或者等同的URN,然后加上年份和月份以避免版本冲突;例如:
[ServiceContract(Namespace = "http://www.idesign.net/2009/06")]
interface IMyContract
{...}
对于局域网服务,可以使用任何有意义的唯一的名称,如MyApplication;例如:
[ServiceContract(Namespace = "MyApplication")]
interface IMyContract
{...}
- 对于运行在Windows XP以及Windows Server 2003 上的局域网应用程序,最好选用自托管,而不是IIS托管。
- 在Windows Vista和Windows Server 2008或更近版本中,最好选用WAS (IIS7)托管,而不是自托管。
- 启用元数据交换。
- 要为客户端配置文件中的所有终结点命名。
- 不要使用SvcUtil或者Visual Studio2008生成配置文件。
- 不要复制代理的代码。如果两个或多个客户端使用相同契约,可以将代理分解到单独的类库中。
- 总是关闭或释放代理。
服务契约
- 总是将ServiceContract特性应用到接口上,而不是类上:
//避免
[ServiceContract]
class MyService
{
[OperationContract]
public void MyMethod()
{...}
}
//正确
[ServiceContract]
interface IMyContract
{
[OperationContract]
void MyMethod();
}
class MyService : IMyContract
{
public void MyMethod()
{...}
}
- 服务契约要添加前缀I:
[ServiceContract]
interface IMyContract
{
[OperationContract]
void MyMethod();
}
- 要避免准属性(Property-Like)操作:
//避免
[ServiceContract]
interface IMyContract
{
[OperationContract]
string GetName();
[OperationContract]
void SetName(string name);
}
- 避免定义只有一个成员的契约。
- 每个服务契约最好只定义3~5个成员。
- 每个服务契约的成员不要超过20个。12个是可能的实际限定。
数据契约
- 避免推断式数据契约(POCO)。总是显式地应用DataContract特性。
- 只在属性或只读公有成员上使用Datamember特性。
- 避免为定制类型显式地执行XML序列化。
- 避免使用消息契约。
- 当使用DataMemberAttribute特性的Order属性时,应该为同一类层级的所有成员分配相同的值。
- 数据契约应实现IExtensibleDataObject接口。使用显式接口实现。
- 避免在ServiceBehavior和CallbackBehavior特性上设置IgnoreExtensionDataObject。应保持它的默认值为false。
- 不要将委托和事件标记为数据成员。
- 不要将.NET的特殊类型例如Type作为操作的参数。
- 不要在操作中接受或返回ADO.NET的DataSet类型和DataTable类型(或者它们的类型安全子类)。应该返回一个与技术无关的表示形式,例如数组。
- 禁止为泛型的类型参数生成散列值,而应该提供易懂的类型名。
实例管理
- 考虑系统的可伸缩性时,最好使用单调实例模式。
- 如果契约设置为SessionMode.NoAllowed,则总是将服务实例配置为InstanceContextMode.PerCall。
- 不要在相同的服务上混合使用会话契约与无会话契约。
- 避免使用单例服务,除非该服务本质上就是单例的。
- 要为会话服务使用有序递送。
- 避免为会话服务停止实例。
- 避免使用分步操作。
- 对于持久服务,总是指定完成操作。
操作与调用
- 不要将单向调用设置为异步调用。
- 不要将单向调用设置为并发调用。
- 对单向操作抛出的异常做出预期。
- 为单向调用启用可靠性。对于单向调用而言,使用有序传递属于可选项。
- 避免在会话服务中定义单向操作。如果定义了,则应将它定义为终止操作:
[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
[OperationContract]
void MyMethod1();
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
void MyMethod2();
}
- 为服务端的回调契约取名时,应使用服务契约名加上Callback后缀:
interface IMyContractCallback
{...}
[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{...}
- 尽量将回调操作标记为单向。
- 只为回调使用回调契约。
- 避免在相同的回调契约中将常规的回调与事件混为一谈
- 事件操作的设计应遵循如下规范:
- void返回类型
- 没有out参数
- 标记为单向操作
- 避免在事件管理中使用原来的回调契约,而应该使用发布-订阅框架
- 避免为回调显式地定义创建(Setup)方法和销毁(Teardown)方法:
[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
Interface IMyContract
{
[OperationContract]
void DoSomething();
[OperationContract]
Void Connect();
[OperationContract]
void Disconnect();
}
Interface IMyContractCallback
{...}
- 使用类型安全的DuplexClientBase<T,C>,而不是DuplexClientBase<T>。
- 使用类型安全的DuplexChannelFactory<T,C>,而不是DuplexChannelFactory<T>。
- 调试或在局域网部署基于WSDualHttpBinding绑定的回调时,应该使用CallbackBaseAddressBehavior特性,并将CallbackPort设置为0:
[CallbackBaseAddressBehavior(CallbackPort = 0)]
Class MyClient:IMyContractCallback
{...}
错误
- 永远都不要在异常抛出之后使用代理实例,即使你捕获了该异常。
- 避免错误契约,并允许WCF屏蔽错误。
- 在异常抛出之后不要重用回调通道,即使你捕获了该异常,因为通道可能已经发生了错误。
- FaultContract特性中包含的类型应该是异常类类型,而不是单纯的可序列化类型:
//避免
[OperationContract]
[FaultContract(typeof(double))]
Double Divide(double number1,double number2);
//正确:
[OperationContract]
[FaultContract(typeof(DivideByZeroException))]
double Divide(double number1, double number2);
- 避免耗时过长的处理,例如IErrorHandler.ProvideFault()方法中的日志操作。
- 测试状态下,服务类与回调类的IncludeExceptionDetailInFaults值均应设置为true,可以在配置文件中配置,也可以采用编程方式:
public class DebugHelper
{
public const bool IncludeExceptionDetailInFaults =
#if DEBUG
true;
#else
false;
#endif
}
[ServiceBehavior(IncludeExceptionDetailInFaults =
DebugHelper.IncludeExceptionDetailInFaults)]
class MyService : IMyContract
{...}
- 构建交付版本时,除了诊断场景,不要将未知异常作为错误返回。
- 如果要提升异常为错误契约,并实现自动地错误日志记录,可以考虑在服务上使用ErrorHandlerBehavior特性:
[ErrorHandlerBehavior]
class MyService : IMyContract
{...}
- 如果要提升异常为错误契约,并实现自动地错误日志记录,可以考虑在回调客户端上使用CallbackErrorHandlerBehaviorAttribute特性:
[CallbackErrorHandlerBehaviorAttribute]
class MyClient : IMyContractCallback
{
public void OnCallback()
{...}
}
事务
- 永远不要直接使用ADO.NET事务。
- 要将TransactionFlow特性应用在契约上,而不是服务类上。
- 不要再服务构造函数中执行事务型工作。
- 如果使用本书术语,服务应该被配置为Client事务或Client/Service事务。避免使用None事务或Service事务。
- 如果使用本书术语,回调应该被配置为Service事务或Service/Client事务。避免使用None事物或Callback事物。
- 使用Client/Service或Service/Callback模式时,应该使用BindingzRequirement特性约束绑定使用事务流。
- 如果服务被配置为None事务或Service事务,则客户端总是会捕获它所抛出来的异常。
- 在使用事务时,允许可靠性和有序传递。
- 在服务操作中,永远不要捕获一个异常,并手动取消事务。
//避免
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod()
{
try
{
...
}
catch
{
rtansaction.Current.Rollback();
}
}
- 如果在事务型操作中捕获了一个异常,那么总是要重新抛出它,或者抛出其它异常。
- 保证事务尽可能短。
- 总是使用默认的隔离级别IsolationLevel.Serializable。
- 不要调用事务中的单向操作。
- 不要调用事务中的非事务型服务。
- 不要访问事务中的非事务型资源(例如文件系统)。
- 对于会话服务,避免在会话关闭时通过自动完成将会话边界与事务边界等同对待。
- 尽量使用TransationalBehavior特性管理会话服务的事务。
[Serializable]
[TransationalBehavior]
class MyService :IMyContrant
{
public void MyMethod()
{...}
}
- 使用一个会话服务后事务型的单例服务时,应使用易失性资源管理器管理状态,同事避免显示的状态相关的编程,或者在完成时依赖WCF停止实例。
- 对于事务性持久服务,总是将SaveStateInOperationTransaction设置为true,从而将事务传播给持久存储。
并发管理
- 总是线程安全访问如下内容:
- 会话服务或单例服务的内存状态
- 回调期间的客户端内存状态
- 共享资源
- 静态变量
- 建议选用ConcurrencyMode.Single(默认值)。它能够启用事务型访问,并提供线程安全的。
- 在ConcurrencyMode.Single模式下,保证会话服务和单例服务的操作耗时短,这是为了避免过长时间地堵塞其他客户端。
- 使用ConcurrencyMode.Multiple时,必须使用自动完成事务。
- 考虑为单调服务使用ConcurrencyMode.Multiple,从而允许并发调用。
- 配置为ConcurrencyMode.Multiple的事务型单例服务,则必须将ReleaseServiceInstanceOnTransactionComplete设置为false:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple,
ReleaseServiceInstanceOnTransactionComplete = false)]
class MySingleton : IMyContract
{...}
- 永远不要在UI线程上实现自托管,也不要通过UI应用程序调用服务。
- 永远不要执行到达调用服务的UI应用程序的回调,除非回调使用SynchronizationContext.Post()方法传递调用。
- 为同步方法和异步方法提供代理时,只能将FaultContractAttribute特性应用到同步方法上。
- 保证异步操作尽可能短。不要将异步操作与长时间操作混为一谈。
- 不要混合使用事务与异步调用
队列服务
- 在客户端,总是调用队列服务之前验证队列(如果可能则验证死信队列)是否可用。可以使用QueuedServiceHelper.VerifyQueue()方法进行验证。
- 在运行一个队列服务(通过ServiceHost<T>自动执行)时,总是要验证队列是否可用。
- 除了是在隔离场景中,要避免设计相同的服务同时工作在队列和非队列中。
- 服务应该参与回放事务。
- 参与回放事务时,要避免在队列服务中执行耗时过长的处理。
- 避免会话队列服务。
- 使用一个单例队列服务时,应使用易失性资源管理器管理单例状态。
- 使用一个单调队列服务时,要显式地将契约和服务配置为单调模式以及无会话状态:
[ServiceConstract(SessionMode = SessionMode.NotAllowed)]
interface IMyContract
{...}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{...}
- 总是将单例队列服务的契约显式地设置为禁止会话:
[ServiceConstract(SessionMode = SessionMode.NotAllowed)]
interface IMyContract
{...}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MyService : IMyContract
{...}
- 客户端应该在事务内调用一个队列服务。
- 在客户端,不要将队列服务代理储存在成员变量中。
- 避免将TimeToLive的值设置为相对较短的值,因为他们会抵消使用队列服务的价值。
- 避免非事务队列。
- 使用响应队列时,要让服务参与到回放事务中,并在事务中将响应放入队列。
- 要让响应服务参与到响应回放事务中。
- 避免在一个队列响应操作中执行耗时过长的处理。
- 对于MSMQ3.0 建议使用一个响应服务,而不是有害队列服务去处理服务自身的重复错误。
- 对于 MSMQ4.0 应对有害消息使用ReceiveErrorHandling.Reject, 除非通过ReceiveErrorHandling.Move 进行高级处理。应避免ReceiveErrorHandling.Fault和ReceiveErrorHandling.Drop。
- 对于 MSMQ4.0 考虑使用响应服务来处理服务回放失败。
- 除非是处理一个会话契约与会话服务,否则永远不要假定队列调用的顺序。
安全
- 总是要保护消息,提供消息证书与完整性。
- 在局域网中,只要保护级别被设置为EncryptAndSign,就可以使用传输安全。
- 在局域网中要避免使用模拟。应将模拟级别设置为TokenImpersonationLevel.Identification.
- 当使用模拟时,客户端应使用TokenImpersonaltionLevel.ImPersonation。
- 应使用声明性安全框架,避免手动配置。
- 永远不要将PrincipalPermission特性直接应用到服务类上:
//总是会失败
[PrincipalPermisson (SecurityAction.Demand,Role = “…”) ]
public calss MyService : IMyContract
{...}
- 避免在服务构造函数中使用需要授权的敏感工作。
- 不管是否要求角色,都应该避免强制要求一个特定的用户:
//避免
[[PrincipalPermisson (SecurityAction.Demand,Role =”John”) ]
public void MyService : IMyMethod
{...}
- 在客户端的回调操作中不要依赖于基于角色的安全机制。
- 对于互联网客户端,总是要使用消息安全。
- 允许客户端与服务证书进行协商(默认)。
- 使用ASP.NET的Provider定制证书。
- 开发一个定制证书存储时,应将它开发为ASP.NET Provider.
- 在使用端点信任( Peer Trust )时要验证证书。
- 尽量将客户端运行在部分信任之下。只能将客户端许可授予:
- 执行
- 显示用户界面
- 连接服务
- 获得本地凭据
- 在获得服务宿主环境时,在完全信任下运行和托管。将Microsoft和ECMA授予完全信任,但要移除其他的代码组,并将他们授予无许可权。
- 在部分信任下托管,只能将宿主和服务许可授予为:
- 执行
- 接受客户端调用
- 获得本地凭据
- 认证和授权客户端
- 如果需要获得本地资源