WCF客户端和服务端的框架体系相互协作,使得开发人员可以按照我们熟悉的方式进行异常的处理:在服务操作执行过程中抛出异常(FaultException),在调用服务时捕获异常,完全感觉不到“分布式”的存在,如同典型的“本地”操作一般。为了实现这样的效果,WCF在内部为我们作了很多。
消息交换是WCF进行通信的唯一手段,消息不仅仅是正常服务调用请求和回复的载体,服务端抛出的异常,甚至是服务的元数据都是通过消息的形式传向客户端的。所以,实现异常与消息之间的转换是整个异常处理体系的核心,而WCF的异常处理框架就着力于完成这样的功能。
我们可以这样来简单地描述WCF异常处理框架的功能实现:WCF服务端将抛出的FaultException异常进行序列化,并根绝消息的SOAP规范(SOAP 1.1或SOAP 1.2)和WS-Addressing规范(WS-Addressing 2004和WS-Addressing 1.0)生成Fault消息。被传入信道层,经过一系列的信道后,该Fault消息最终借助于传输层返回到客户端;客户端信道层接收到该Fault消息并经过相应的处理后,被反序列化。反序列化的结果即实现对FaultException的重建,WCF最终将重建的FaultException异常抛出,对于最终的开发者而言,感觉就像服务端抛出的FaultException直接被客户端捕获了一样。在上面的内容中我们说过:WCF并不直接进行FaultException和Fault消息之间的转换,而是借助于MessageFault这一中间对象。右图体现了错误(Fault)在整个WCF异常处理过程中的流转。
通过中篇的介绍,我们知道:对FaultException进行序列化和反序列化的核心对象是FaultFormatter,了解WCF整个异常处理框架的实现原理首先需要知道FaultFormatter是如何创建的。
一、FaultFormatter是如何创建的?
WCF的服务端和客户端均需要一个FaultFormatter对象,分别用于对FaultException异常对象的序列化和反序列化,现在我们分别介绍FaultFormatter对象在服务端和客户端是如何被创建的。
1、FaultFormatter(DispatchFaultFormatter)在服务端如何被创建
FaultFormatter在服务端创建于服务寄宿之时。具体来讲,在ServiceHost被初始化过程中,WCF会为服务的每个终结点创建相应的终结点分发器(EndpointDispatcher)。而对于每一个被创建出来的终结点分发器都具有一个相应的分发运行时(DispatchRuntime)。DispatchRuntime是整个WCF运行时框架的核心,一系列的对象和组件被它引用以实现对整个消息分发和操作执行行为的控制。(关于整个服务寄宿在WCF服务端框架内的执行流程,在《WCF技术剖析(卷1)》的第7章有详细的介绍。)
在DispatchRuntime的初始化过程中,WCF会根据服务的描述创建一系列的DispatchOperation对象。DispatchOperation对象可以看成是某个服务操作在运行时的表示,最终对服务操作的执行就是通过它来完成的。具体来讲,WCF会获取当前终结点的契约描述(ContractDescription),遍历每一个操作描述(OperationDescription)借此创建相应的DispatchOperation。ServiceEndpoint、ContractDescription和OperationDescription三者之间的关系通过下面的类型定义代码一目了然。
1: public class ServiceEndpoint
2: {
3: //其他成员
4: public ContractDescription Contract { get; }
5: }
6: public class ContractDescription
7: {
8: //其他成员
9: public OperationDescriptionCollection Operations { get; }
10: }
11: public class OperationDescription
12: {
13: //其他成员
14: public FaultDescriptionCollection Faults { get; }
15: }
对于作为操作描述的OperationDescription类型,有一个与本章主题相关的类型为System.ServiceModel.Description.FaultDescriptionCollection的集合属性:Faults,表示与本操作相关的所有错误描述的集合。该集合的每一个元素为System.ServiceModel.Description.FaultDescription对象,FaultDescription对象是对应用在操作上的FaultContractAttribute特性反射所得。FaultDescription的定义如下,可见它与FaultContractAttribute的结构定义完全一样。
1: public class FaultDescription
2: {
3: //其他成员
4: public string Action { get; internal set; }
5: public Type DetailType { get; set; }
6: public bool HasProtectionLevel { get; }
7: public string Name { get; set; }
8: public string Namespace { get; set; }
9: public ProtectionLevel ProtectionLevel { get; set; }
10: }
当WCF服务端运行时以操作描述为基础创建相应的DispatchOperation后,会根据错误描述创建FaultFormatter对象,声明类型为IDispatchFaultFormatter。
1: public sealed class DispatchOperation
2: {
3: //其他成员
4: public SynchronizedCollection<FaultContractInfo> FaultContractInfos
5: { get; }
6: internal IDispatchFaultFormatter FaultFormatter { get; set; }
7: }
从上面的代码片断还会看到,除了一个内部属性FaultFormatter之外,还具有一个类型为SynchronizedCollection<FaultContractInfo>的集合属性:FaultContractInfos。而作为集合元素的System.ServiceModel.Dispatcher.FaultContractInfo对象表示错误契约相关的信息,该集合于操作描述(OperationDescription)的Faults属性相匹配。实际上,FaultContractInfo仅仅包含两项用于实现序列化的信息:错误明细类型和Action,这可以从FaultContractInfo的定义看出来:
1: public class FaultContractInfo
2: {
3: //其他成员
4: public string Action { get; }
5: public Type Detail { get; }
6: }
序列化的序列化和反序列化需要以类型的确定为前提,所以FaultFormatter在进行序列化或者反序列化过程之前,需要确定错误明细的类型;此外,不知道读者有没有注意到这一点:MessageFault并没有一个Action属性.对于一个SOAP消息来说,Action是一个必不可少的WS-Addressing报头;而FaultException类型也具有相应的Action属性定义。在WCF服务端框架内,在实现FaultException异常对象相Fault消息转换的过程中,除了提供与FaultException对等的MessageFault之外,还需要提供FaultException的Action属性值。这正是为何FaultFormatter在进行序列化工作的时候依赖于一个FaultContractInfo集合的原因。实际上,在构建System.ServiceModel.Dispatcher.FaultFormatter这么一个对象的时候,就需要传入一个这样的集合对象,这可以从FaultFormatter的构造函数看出来:
1: internal class FaultFormatter : IClientFaultFormatter, IDispatchFaultFormatter
2: {
3: //其他成员
4: internal FaultFormatter(Type[] detailTypes);
5: internal FaultFormatter(SynchronizedCollection<FaultContractInfo> faultContractInfoCollection);
6: }
序列化过程中对Action的指定,WCF内部采用这样一个规则:如果FaultException对象本身具有一个Action,则返回该值;如果没有,则在FaultContractInfo列表中找到一个错误明细类型相匹配的FaultContractInfo对象,如果该对象具有一个有效的Action属性,则返回之;如果该FaultContractInfo仍然没有定义Action属性,那么WCF会根据采用的WS-Addressing版本选择默认的Action值:
1: WS-Addressing 2004:http://www.w3.org/2005/08/addressing/soap/fault
2: WS-Addressing 1.0:http://schemas.xmlsoap.org/ws/2004/08/addressing/fault
2、FaultFormatter(ClientFaultFormatter)在客户端如何被创建
FaultFormatter在客户端的创建方式与服务端有点相似,同样是基于FaultDescription的方式创建,所以在这里我仅仅是地对整个过程作一个概括性的介绍。
客户端通过ChannelFactory<TChannel>或者DuplexChannelFactory<TChannel>创建用于进行服务调用的服务代理,终结点随着这两个对象的创建而创建。ChannelFactory<TChannel>或者DuplexChannelFactory<TChannel>被开启之后,客户端运行时(Client Runtime)被WCF创建出来。在客户端运行时初始化过程中,WCF为每一个操作创建ClientOperation对象(《WCF技术剖析(卷1)》第8章对整个WCF客户端执行流程有详细的介绍)。这与在服务端初始化分发运行时(DispatchRuntime)与DispatchOperation的创建类似。和DispatchOperation定义一样,ClientOperation同样有两个同名属性:FaultContractInfos和FaultFormatter,不过FaultFormatter的类型为IClientFaultFormatter。
1: public sealed class ClientOperation
2: {
3: //其他成员
4: public SynchronizedCollection<FaultContractInfo> FaultContractInfos { get; }
5: internal IClientFaultFormatter FaultFormatter { get; set; }
6: }
3、DataContractSerializerFaultFormatter还是XmlSerializerFaultFormatter
从上面的FaultFormatter的介绍中我们可以知道,介于不同的序列化方式的需要,WCF异常处理框架使用两个不同的FaultFormatter:DataContractSerializerFaultFormatter还是XmlSerializerFaultFormatter,分别利用DataContractSerializer和XmlSerializer这两个不同的序列化器实现对FaultException异常对象的序列化和反序列化。那么具体对FaultFormatter的选择是如何实现的呢?
在WCF服务端和客户端的异常处理框架体系内,对FaultFormatter的提供机制最终是通过DispatchOperation和ClientOperation的FaultFormatter属性实现的。在DispatchOperation和ClientOperation被创建的时候,并不会伴随着FaultFormatter的创建。在默认的情况下,WCF采用懒惰加载(Lazy Loading)的方式创建FaultFormatter,也就是说WCF在真正使用到FaultFormatter的时候,才动态地创建该对象。通过这种方式创建的永远是DataContractSerializerFaultFormatter,这也正是WCF采用DataContractSerializerFaultFormatter作为默认的FaultFormatter的原因。
我们可以在服务契约、服务类型和服务操作方法上面应用XmlSerializerFormatAttribute这么一个特性让WCF采用XmlSerializer作为序列化器对FaultException异常进行序列化和反序列化。最终体现在WCF内部会根据这么一个特性选择XmlSerializerFaultFormatter而不是DataContractSerializerFaultFormatter作为最终的FaultFormatter。那么,对XmlSerializerFaultFormatter的选择又是如何实现的呢?
实际上,WCF对XmlSerializerFaultFormatter的选择是同一个类型XmlSerializerOperationBehavior的特殊的操作行为(OperationBehavior)实现的。XmlSerializerOperationBehavior定义如下,我们可以看到,在ApplyClientBehavior和ApplyDispatchBehavior,分别将当前的ClientOperation和DispatchOperation的FaultFormatter设置成为XmlSerializerFaultFormatter。
1: public class XmlSerializerOperationBehavior : IOperationBehavior, IWsdlExportExtension
2: {
3: //其他成员
4: void IOperationBehavior.AddBindingParameters(OperationDescription description, BindingParameterCollection parameters);
5: void IOperationBehavior.ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
6: {
7: proxy.FaultFormatter = this.CreateXmlSerializerFaultFormatter();//伪代码
8: }
9: void IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
10: {
11: dispatch.FaultFormatter = this.CreateXmlSerializerFaultFormatter();//伪代码
12: }
13: void IOperationBehavior.Validate(OperationDescription description);
14: }
无论是客户端还是服务端,在初始化操作描述的时候,WCF会通过反射确定服务契约或者操作方法上面是否应用了XmlSerializerFormatAttribute特性,从而决定是否会添加XmlSerializerOperationBehavior这么一个操作行为到该操作的行为列表中。
二、异常的抛出、序列化、反序列化与捕获
现在系统的介绍WCF异常处理的整个流程,由于前面已经作了足够的铺垫,具体涉及到WCF对整个异常处理流程的控制,反而没有太多内容可讲。
服务操作的中中执行最终通过DispatchOperation的OperationInvoker执行。如果在执行过程中,抛出出FaultException异常,WCF会获取当前DispatchOperation的FaultFormatter,调用Serialze方法对异常对象进行序列化。序列化完成后得到相应的MessageFault对象和Action值,这两个值最终通过调用Message的CreateMessage静态方法生成一个Fault消息对象。
而客户端的服务调用最终通过ClientOperation对象完成。当调用服务获得回复消息后,如何回复消息是Fault消息,WCF会调用MessageFault的CreateFault将消息转化成MessageFault对象,并获取Action值。最终通过ClientOperation得到FaultFormatter,调用Deserialize方法并传入MessageFault对象和Action值通过反序列化在客户端重建FaultException异常对象并将其抛出来。
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。