WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[中篇]2012-11-17 博客园 Artech在[上篇]中,我们分别站在消息交换和编程的角度介绍了SOAP Fault和FaultException异常。在服务执行过程中,我们手工抛出FaultException异常,WCF服务端框架会对该异常对象进行序列化病最终生成Fault消息。当WCF客户端框架介绍到该Fault消息之后,会做一项相反的操作:对Fault消息中进行解析和反序列化,重新生成并抛出FaultException异常。WCF框架自动为我们作了这么多“幕后”工作,使得开发人员可以完全采用编写一般的.NET应用程序的模式进行异常的处理:在错误的地方抛出相应异常,对于潜在出错的方法调用进行相应的异常捕获和处理。所以,WCF的异常处理框架的核心功能就是实现FaultException异常和Fault消息之间的转换,接下来我们着重来讨论这个话题。
一、FaultException异常和Fault消息之间的纽带:MessageFault对于WCF的异常处理框架,其本身并不直接进行FaultException异常和Fault消息之间的转换,而是通过另外一个作为中介的对象来完成的,这个对象就是这一小节我们讲述的重点:MessageFault。Message(Fault)、MessageFault和FaultException通过如图1描述的“三角”关系实现了相互之间的转化。
图1 Message(Fault)、Message和FaultException“三角”转换关系在消息介绍MessageFault之前,我们先来看看MessageFault的定义。MessageFault定义在命名空间System.ServiceModel.Channels下,下面的代码是MessageFault的定义。
1: public abstract class MessageFault
2: {
3://其他成员
4: public static MessageFault CreateFault(Message message, int maxBufferSize);
5: public static MessageFault CreateFault(FaultCode code, FaultReason reason);
6: public static MessageFault CreateFault(FaultCode code, string reason);
7: public static MessageFault CreateFault(FaultCode code, FaultReason reason, object detail);
8: public static MessageFault CreateFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer serializer);
9: public static MessageFault CreateFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer serializer, string actor);
10: public static MessageFault CreateFault(FaultCode code, FaultReason reason, object detail, XmlObjectSerializer serializer, string actor, string node);
11:
12: public T GetDetail<T>();
13: public T GetDetail<T>(XmlObjectSerializer serializer);
14: public XmlDictionaryReader GetReaderAtDetailContents();
15:
16: public void WriteTo(XmlDictionaryWriter writer, EnvelopeVersion version);
17: public void WriteTo(XmlWriter writer, EnvelopeVersion version);
18:
19: public virtual string Actor { get; }
20: public abstract FaultCode Code { get; }
21: public abstract bool HasDetail { get; }
22: public bool IsMustUnderstandFault { get; }
23: public virtual string Node { get; }
24: public abstract FaultReason Reason { get; }
25: }
从上面给出的对MessageFault并不复杂的定义可以看出,它的属性成员和FaultException,以及SOAP Fault的5个子元素是想匹配的:Code、Reason、Node、Actor(对于SOAP 1.2规范中SOAP Fault的Role元素,在SOAP 1.1中的名称为Actor)。而另一个元素Detail则可以通过两个泛型方法GetDetail<T>获得。由于此操作需要对错误明细对象进行反序列化,所以需要指定错误明细类型对应的序列化器,默认情况下采用的是DataContractSerializer。而属性IsMustUnderstandFault表述此错误是否是由于识别 SOAP 标头失败而造成的,实际上,它和FaultCode的IsPredefinedFault向对应,主要具有预定义的Code,IsMustUnderstandFault就返回True。通过MessageFault众多的CreateFault静态方法,我们可以以不同的组合方式指定构成SOAP Fault的5个元素。如果指定了错误明细对象,需要指定与之匹配的序列化器以实现对其的序列化和反序列化。两个重载的WirteTo方法实行对MessageFault进行序列化,并将序列化后的XML通过XmlDictionaryWriter或者XmlWriter写入掉相应的“流”中。由于不同的SOAP规范的版本(SOAP 1.1和SOAP 1.2)对Message Fault的结构进行了不同的规定,所有在调用WirteTo的时候需要显式地指定基于那个版本进行写入(SOAP的版本通过EnvelopeVersion表示)。下面的示例代码中,我们创建了一个MessageFault对象,分别针对SOAP 1.1和SOAP 1.2写到两个不同的XML文件中。读者可以仔细辨别最终生成的Message Fault到底有多大的差别。
1: using System.Collections.Generic;
2: using System.Diagnostics;
3: using System.IO;
4: using System.Runtime.Serialization;
5: using System.ServiceModel;
6: using System.ServiceModel.Channels;
7: using System.Text;
8: using System.Xml;
9: namespace MessageFaultDemos
10: {
11: class Program
12: {
13: static void Main(string[] args)
14: {
15: FaultCode code = FaultCode.CreateSenderFaultCode(new FaultCode("CalculationError", "http://www.artech.com/"));
16: IList<FaultReasonText> reasonTexts = new List<FaultReasonText>();
17: reasonTexts.Add(new FaultReasonText("The input parameter is invalid!","en-US"));
18: reasonTexts.Add(new FaultReasonText("输入参数不合法!", "zh-CN"));
19: FaultReason reason = new FaultReason(reasonTexts);
20:
21: CalculationError detail = new CalculationError("Divide", "被除数y不能为零!");
22: MessageFault fault = MessageFault.CreateFault(code, reason, detail, new DataContractSerializer(typeof(CalculationError)), "http://http://www.artech.com/calculatorservice", "http://http://www.artech.com/calculationcenter");
23:
24: string fileName1 = @"fault.soap11.xml";
25: string fileName2 = @"fault.soap12.xml";
26: WriteFault(fault, fileName1, EnvelopeVersion.Soap11);
27: WriteFault(fault, fileName2, EnvelopeVersion.Soap12);
28: }
29:
30: static void WriteFault(MessageFault fault, string fileName, EnvelopeVersion version)
31: {
32:using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
33: {
34: using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, false))
35: {
36: fault.WriteTo(writer, version);
37: Process.Start(fileName);
38: }
39: }
40: }
41: }
42: }