通用设计指南

1、所有的服务必须遵循以下原则:

(1)服务是安全的。

(2)服务在系统中应保持状态一致。

(3)服务是线程安全的。

(4)服务可以被并发客户端访问。

(5)服务是可靠的。

(6)服务是健壮的。

2、服务应遵循以下可选原则:

(1)服务是可互操作的。

(2)服务的规模是不变的。

(3)服务是可用的。

(4)服务是及时响应的。

(5)服务是受限的,阻塞客户端的时间不能过长。

3、避免消息契约

WCF基础

1、应该将服务代码放入到类库中,而不是放到宿主EXE中。

2、不要为服务类提供参数构造函数,除非托管的服务是明确的单例服务。

3、在相关的绑定中启用可靠性。

4、要为契约提供有意义的命名空间。对于公开向外的服务,可以使用公司的URL或者等同的URN,然后加上年份和月份以避免版本冲突,例如:

  1. [ServiceContract(Namespace="http://www.idesign.net/2007/08")]  
  2. interface IMyContract  
  3. {...} 

对于局域网服务,可以使用任何有意义的唯一的名称,如MyApplication,例如:

  1. [ServiceContract(Namespace="MyApplication")]  
  2. interface IMyContract  
  3. {...} 

5、对于运行在Windows XP以及Windows Server 2003上的局域网应用程序,最好选用自托管,而不是IIS托管。

6、在Windows Vista中,最好选用WAS(IIS7)托管,而不是自托管。

7、使用ServiceHost<T>。

8、启用元数据交换。

9、要为客户端配置文件中的所有终结点命名。

10、不要使用SvcUtil或者Visual Studio 2005生成配置文件。

11、不要复制代理的代码。如果两个或多个客户端使用相同契约,可以将代理分解到单独的类库中。

12、总是关闭或释放代理。

服务契约

1、总是将ServiceContractAttribute特性应用到接口上,而不是类上:

  1. //避免  
  2. [ServiceContract]  
  3. class MyService  
  4. {  
  5.     [OperationContract]    
  6.     public void MyMethod()  
  7.     {...}    
  8. }   
  9.  
  10. //正确  
  11. [ServiceContract]  
  12. interface IMyContract  
  13. {  
  14.     [OperationContract]  
  15.     void MyMethod();  
  16. }  
  17. class MyService:IMyContract  
  18. {  
  19.     public void MyMethod()  
  20.     {...}  

2、服务契约要添加前缀I:

  1. [ServiceContract]  
  2. interface IMyContract  
  3. {...} 

3、避免准属性(Property-Like)操作:

  1. [ServiceContract]  
  2. interface IMyContract  
  3. {  
  4.     [OperationContract]  
  5.     string GetName();  
  6.  
  7.     [OperationContract]  
  8.     void SetName(string name);  

4、避免定义只有一个成员的契约。

5、每个服务契约最好只定义三到五个成员。

6、每个服务契约的成员不要超过20个。12个是可能的实际限定。

数据契约

1、只在属性或只读公有成员上使用DataMemberAttribute特性。

2、避免为定制类型显式地执行XML序列化。

3、当使用DataMemberAttribute特性的Order属性时,应该为同一类层级的所有成员分配相同的值。

4、数据契约应实现IExtensibleDataObject接口。

5、避免在ServiceBehavior和CallbackBehavior特性上设置IgnoreExtensionDataObject,应保持它的默认值false。

6、不要将委托和事件标记为数据成员。

7、不要将.NET的特殊类型例如Type作为操作的参数。

8、不要在操作中接收或返回ADO.NET的DataSet类型和DataTable类型(或者它们的类型安全子类)。应该返回一个与技术无关的表示形式,例如数组。

9、禁止为泛型的类型参数生成哈希值,而应该提供易懂的类型名。

10、避免使用SvcUtil的/ct开关或者其他跨越服务边界的共享程序集类型的方法。

实例管理

1、最好使用单调实例模式。

2、避免使用会话服务。

3、如果契约选择使用SessionMode.Required,则应显式地将服务实例设置为InstanceContextMode.PerSession。

4、如果契约选择使用SessionMode.NotAllowed,则应将服务实例配置为InstanceContextMode.PerCall。

5、不要在相同的服务上混合使用会话契约与单调契约。

6、避免使用单例服务,除非该服务本质上就是单例的。

7、要为会话服务使用有序传递。

8、避免为会话服务停止实例。

9、避免使用分步操作。

操作与调用

1、不要将单向调用设置为异步调用。

2、不要将单向调用设置为并发调用。

3、单向操作不应该包含异常。

4、为单向调用启用可靠性。对于单向调用而言,使用有序传递属于可选项。

5、避免在会话服务中定义单向操作。如果定义了,则应将它定义为终止操作:

  1. [ServiceContract(SessionMode=SessionMode.Required)]  
  2. interface IOrderManager  
  3. {  
  4.     [OperationContract]  
  5.     void SetCustomerID(int customerId);  
  6.  
  7.     [OperationContract(IsInitiating=false)]  
  8.     void AddItem(int itemId);  
  9.  
  10.     [OperationContract(IsInitiating=false)]  
  11.     decimal GetTotal();  
  12.  
  13.     [OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=true)]  
  14.     void ProcessOrders();  

6、为服务端的回调契约取名时,应使用服务契约名加上Callback后缀:

  1. interface IMyContractCallback  
  2. {...}  
  3. [ServiceContract(CallbackContract=typeof(IMyContractCallback))]  
  4. interface IMyContract  
  5. {...} 

7、尽量将回调操作标记为单向。

8、只为回调使用回调契约。

9、避免在相同的回调契约中将常规的回调与事件混为一谈。

10、事件操作的设计应遵循如下规范:

(1)void返回类型

(2)没有out参数

(3)标记为单向操作

11、避免在事件管理中使用原来的回调契约,而应该使用发布-订阅框架。

12、避免为回调显式地定义创建(Setup)方法和销毁(Teardown)方法:

  1. [ServiceContract(CallbackContract=typeof(IMyContractCallback))]  
  2. interface IMyContract  
  3. {  
  4.     [OperationContract]  
  5.     void DoSomething();  
  6.       
  7.     [OperationContract]  
  8.     void Connect();  
  9.  
  10.     [OperationContract]  
  11.     void Disconnect();  
  12. }  
  13. interface IMyContractCallback  
  14. {...} 

13、使用类型安全的DuplexClientBase<T,C>,而不是DuplexClientBase<T>。

14、使用类型安全的DuplexChannelFactory<T,C>,而不是DuplexChannelFactory<T>。

15、调试或在局域网部署基于WSDualHttpBinding绑定的回调时,应该使用CallbackBaseAddressBehaviorAttribute特性,并将CallbackPort设置为0:

  1. [CallbackBaseAddressBehavior(CallbackPort=0)]  
  2. class MyClient:IMyContractCallback  
  3. {...} 

错误

1、永远都不要在异常抛出之后使用代理实例,即使你捕获了该异常。

2、在异常抛出之后不要重用回调通道,即使你捕获了该异常,因为通道可能已经发生了错误。

3、FaultContractAttribute特性中包含的类型应该是异常类类型,而不是单纯的可序列化类型:

  1. //避免:  
  2. [OperationContract]  
  3. [FaultContract(typeof(double))]  
  4. double Divide(double number1,double number2);  
  5.  
  6. //正确:  
  7. [OperationContract]  
  8. [FaultContract(typeof(DivideByZeroException))]  
  9. double Divide(double number1,double number2); 

4、避免耗时过长的处理,例如IErrorHandler.ProvideFault()方法中的日志操作。

5、测试状态下,服务类与回调类的IncludeExceptionDetailInFaults值均应设置为true,可以在配置文件中配置,也可以采用编程方式:

  1. public class DebugHelper  
  2. {  
  3.     public const bool IncludeExceptionDetailInFaults=  
  4. #if DEBUG  
  5.     true;  
  6. #else  
  7.     false;  
  8. #endif  
  9. }  
  10.  
  11. [ServiceBehavior(IncludeExceptionDetailInFaults=DebugHelper.IncludeExceptionDetailInFaults)]  
  12. class MyService:IMyContract  
  13. {...} 

6、构件交付版本时,除了诊断场景,不要将未知异常作为错误返回。

7、如果要提升异常为错误契约,并实现自动地错误日志记录,可以考虑在服务上使用ErrorHandlerBehaviorAttribute特性:

  1. [ErrorHandlerBehavior]  
  2. class MyService:IMyContract  
  3. {...} 

8、如果要提升异常为错误契约,并实现自动地错误日志记录,可以考虑在回调客户端上使用CallbackErrorHandlerBehaviorAttribute特性:

  1. [CallbackErrorHandlerBehavior(typeof(MyClient))]  
  2. public partial class MyClient:IMyContractCallback  
  3. {  
  4.     public void OnCallback()  
  5.     {...}  
  6. }  

事务

1、永远不要直接使用ADO.NET事务。

2、要将TransactionFlowAttribute特性应用在契约上,而不是服务类上。

3、不要在服务构造函数中执行事务型工作。

4、服务应该被配置为Client事务或Client/Service事务。避免使用None事务或Service事务。

5、回调应该被配置为Service事务或Service/Callbacks事务。避免使用None事务或Callback事务。

 6、使用Client/Service或Service/Callback模式时,应该使用BindingRequirement特性约束绑定使用事务流。

7、如果服务被配置为None事务或Service事务,则客户端总是会捕获它所抛出的异常。

8、总是要求操作至少被配置为支持事务:

  1. [ServiceContract]  
  2. interface IMyContract  
  3. {  
  4.     [OperationContract]  
  5.     [TransactionFlow(TransactionFlowOption.Allowed)]  
  6.     void MyMethod(...);  

9、使用事务时应启用可靠性和有序传递。

10、在服务操作中,永远不要捕获一个异常,然后手动取消事务:

  1. //避免  
  2. [OperationBehavior(TransactionScopeRequired=true)]  
  3. public void MyMethod()  
  4. {  
  5.     try 
  6.     {  
  7.         ...  
  8.     }  
  9.     catch 
  10.     {  
  11.         Transaction.Current.Rollback();  
  12.     }  

11、如果在事务型操作中捕获了一个异常,那么总是要重新抛出它,或者抛出其他异常。

12、总是使用默认的隔离级别IsolationLevel.Serializable。

13、不要调用事务中的单向操作。

14、不要调用事务中的非事务型服务。

15、不要访问事务中的非事务型资源(例如文件系统)。

16、避免事务型会话服务。

17、建议使用单调事务型服务。

18、使用一个会话服务或事务型的单例服务时,应使用易失性资源管理器管理状态,同时避免显式的状态相关的编程,或者在完成时依赖WCF停止实例。

并发管理

1、总是线程安全访问如下内容:

(1)会话服务或单例服务的内存状态

(2)回调期间的客户端内存状态

(3)共享资源

(4)静态变量

2、建议选用ConcurrencyMode.Single(默认值)。它能够启用事务型访问,而本身则是线程安全的。

3、在ConcurrencyMode.Single模式下,保证会话服务和单例服务的操作耗时短,这是为了避免过长时间的阻塞其他客户端。

4、使用ConcurrencyMode.Multiple时,必须使用自动完成事务。

5、考虑为单调服务使用ConcurrencyMode.Multiple,从而允许并发调用。

6、配置为ConcurrencyMode.Multiple的事务型单例服务,则必须将ReleaseServiceInstanceOnTransactionComplete设置为false:

  1. [ServiceBehavior(InstanceContextMode=InstanceMode.Single,ConcurrencyMode=ConcurrencyMode.Multiple,ReleaseServiceInstanceOnTransactionComplete=false)]  
  2. class MySingleton:IMyContract  
  3. {...} 

7、永远不要在UI线程上实现子托管,也不要通过UI应用程序调用服务。

8、永远不要执行到达调用服务的UI应用程序的回调,除非回调使用SynchronizationContext.Post()方法传递调用。

9、只能在客户端使用异步调用模式。

10、为同步方法和异步方法提供代理时,只能将FaultContractAttribute特性应用到同步方法上。

11、不要混合使用事务与异步调用。

队列服务

1、在调用队列服务之前总是要验证队列(如果可用则验证死信队列)是否可用。可以使用QueuedServiceHelper.VerifyQueue<T>()方法进行验证。

2、在运行一个队列服务(通过ServiceHost<T>自动执行)时,总是要验证队列是否可用。

3、除了是在隔离场景中,要避免设计相同的服务同时工作在队列和非队列中。

4、要为队列服务启用元数据交换。

5、服务应该参与回放事务。

6、参与回放事务时,要避免在队列服务中执行耗时过长的处理。

7、避免会话队列服务。

8、使用一个单例队列服务时,应使用易失性资源管理器管理单例状态。

9、使用一个单调队列服务时,要显式地将契约和服务配置为单调模式以及无会话状态:

  1. [ServiceContract(SessionMode=SessionMode.NotAllowed)]  
  2. interface IMyContract  
  3. {...}  
  4.  
  5. [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]  
  6. class MyService:IMyContract  
  7. {...} 

10、总是将单例队列服务的契约显式地设置为禁止会话:

  1. [ServiceContract(SessionMode=SessionMode.NotAllowed)]  
  2. interface IMyContract  
  3. {...}  
  4.  
  5. [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]  
  6. class MyService:IMyContract  
  7. {...}   

11、客户端应该在事务内调用一个队列服务。

12、在客户端,不要将队列服务代理存储在成员变量中。

13、避免将TimeToLive的值设置为相对较短的值,因为它们会抵消使用队列服务的价值。

14、永远不要使用易失性队列。

15、使用响应队列时,要让服务参与到回放事务中,并在事务中将响应放入队列。

16、要让响应服务参与到响应回放事务中。

17、避免在一个队列响应操作中执行耗时过长的处理。

18、建议选用一个响应服务,而不是有害队列服务去处理服务自身的重复错误。

19、除非是处理一个会话契约与会话服务,否则永远不要假定队列调用的顺序。

安全

1、总是要保护消息,提供消息证书与完整性。

2、在局域网中,只要保护级别被设置为EncryptAndSign,就可以使用不带消息安全的传输安全。

3、在局域网中要避免使用模拟。应将模拟级别设置为TokenImpersonationLevel.Identification。

4、当使用模拟时,客户端应使用TokenImpersonationLevel.Impersonation。

5、应使用声明型安全框架,避免手动配置。

6、永远不要将PrincipalPermissionAttribute特性直接应用到服务类上:

  1. //总是会失败  
  2. [PrincipalPermission(SecurityAction.Demand,Role="...")]  
  3. public class MyService:IMyContract  
  4. {...} 

7、避免在服务构造函数中使用需要授权的敏感工作。

8、不管是否要求角色,都应避免强制要求一个特定的用户:

  1. //避免  
  2. [PrincipalPermission(SecurityAction.Demand,Name="John")]  
  3. public void MyMethod()  
  4. {...} 

9、在客户端的回调操作中不要依赖于基于角色的安全机制。

10、对于互联网客户端,总是要使用消息安全。

11、允许客户端与服务证书进行协商(默认)。

12、使用ASP.NET的Provider定制证书。

13、开发一个定制证书存储时,应将它开发为ASP.NET Provider。

14、在使用端点信任(Peer Trust)时要验证证书。