對於上一篇文章 (WCF基本異常處理模式:[上篇]、[中篇]、[下篇]),主要是站在最終開發者的角度對WCF關於異常處理編程模式進行了介紹,接下來,我們需要將我們的目光轉移到WCF框架內部,深入剖析整個WCF異常處理流程。在基於SOAP的消息交換過程中,異常最終通過Fault消息承載,所以很自然地,接下來的介紹從SOAP Fault說起。
一、 從SOAP Fault說起(基於SOAP 1.2)
服務調用的最終實現通過消息交換完成,WCF本質上可以看成是一個消息處理的框架。消息,不但承載著正常服務調用的請求和回復,在出現異常時,消息依然是錯誤信息的載體。今年來,盡管隨著REST的迅速發展,基於POX(Plain of XML)消息交換大行其道;AJAX的持續升溫,又是的基於非XML(JSON)的消息開始火熱,但是不可否認,在今後不短的一段時間內SOAP依然是主流。看看現在有多少WS-*規范或者標准是建立在SOAP之上,就應該對這個論斷不會有懷疑。
W3C先後出台了兩個關於SOAP的規范:SOAP 1.1和SOAP 1.2。在《WCF技術剖析(卷1)》的第5章,我曾經按照SOAP 1.2規范對SOAP作了全面的介紹,這其中就包括對SOAP Fault。由於異常在消息交換中通過Fault消息承載,所以很多必要對SOAP Fault的相關規范作一下重申,至於SOAP的其他相關的內容,在這裡就不再作重復的介紹了。
要了解SOAP 1.2下關於SOAP 1.2對SOAP Fault的規范,我們首先來看看一下一個具有完整結構的Fault SOAP。
1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
2: <s:Header>
3: <a:Action s:mustUnderstand="1">http://www.artech.com/ICalculator/DivideCalculationErrorFault</a:Action>
4: </s:Header>
5: <s:Body>
6: <s:Fault>
7: <s:Code>
8: <s:Value>s:Sender</s:Value>
9: <s:Subcode>
10: <s:Value xmlns:a="http://www.artech.com/">a:CalculationError</s:Value>
11: </s:Subcode>
12: </s:Code>
13: <s:Reason>
14: <s:Text xml:lang="zh-CN">被除數y不能為零!</s:Text>
15: </s:Reason>
16: <s:Node>http://http://www.artech.com/calculationcenter</s:Node>
17: <s:Role>http://http://www.artech.com/calculatorservice</s:Role>
18: <s:Detail>
19: <CalculationError xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/">
20: <Message>被除數y不能為零!</Message>
21: <Operation>Divide</Operation>
22: </CalculationError>
23: </s:Detail>
24: </s:Fault>
25: </s:Body>
26: </s:Envelope>
上面一個SOAP是非常完整的Fault消息,它的主體(Body)部分包含了構成SOAP Fault所有類型的元素(必需的和可選的),接下來,我們就在這個Fault消息的基礎上介紹SOAP 1.2規范下對SOAP Fault的相關規定。
在SOAP 1.2規范中規定,SOAP Fault作為Fault SOAP消息的主體,用於承載錯誤相關的信息。具體地,對於SOAP Fault元素,有作了如下幾點規范:
元素名稱必須為Fault;
元素命名空間必須為http://www.w3.org/2003/05/soap-envelope;
元素包含如下子元素(圓括號中的代表各子元素在上面給定的Fault SOAP中對應的值):
1、一個必須的Code元素表示錯誤代碼;
2、一個比如的Reason元素表示出錯的原因;
3、一個可選的Node元素表示導致出錯的SOAP節點(SOAP Node);
4、一個可選的Role元素表示SOAP節點對應的角色;
5、一個可選的Detail表述對錯誤的詳細描述。
接下來,我們對組成SOAP Fault的五個子元素進行簡單的介紹。
1、Fault Code元素
SOAP Fault的Code元素,是一個用以表示錯誤類型的代碼,該錯誤代碼可以大致看作對錯誤的一種分類。SOAP 1.2對Code元素的格式作了如下的規范:
元素名稱必須為“Code”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”;
Code元素只能先後包含如下兩個類型的子元素;
1、一個必須的Value元素用以定義錯誤代碼;
2、一個可選的SubCode元素用以定義錯誤子代碼。
而對於Value元素的格式,又具有如下的規范:
元素名稱必須為“Value”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”;
元素類型為“env:faultCodeEnum”枚舉,下面的表格列出了所有的可選枚舉值。
枚舉值 含義 VersionMismatch 命名空間或者名稱和規定的SOAP規范不匹配 MustUnderstand 目標SOAP結點不能理解並處理mustUnderstand屬性為“true”或者“1”的SOAP報頭 DataEncodingUnknown SOAP報頭或者主體的數據編碼方式不被目標SOAP結點支持 Sender 消息格式合法或者缺少必要的數據 Receiver SOAP結點處理消息出現錯誤
而SubCode元素相關的規范定義如下:
元素名稱必須為“SubCode”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”
SubCode元素只能包含以下兩種類型的子元素:
1、必須Value元素:名稱為“Value”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”,類型為“xs:QName”,一般將具體應用定義錯誤代碼用作該元素的值
2、可選的Subcode元素
可見,SOAP Code是一種具有層級關系的(Hierarchical)的結構(Code的具有一個Code結構的SubCode)。在上面給出的Fault消息,就具有一個具有兩層結構的SOAP Code:
1: <s:Code>
2: <s:Value>s:Sender</s:Value>
3: <s:Subcode>
4: <s:Value xmlns:a="http://www.artech.com/">a:CalculationError</s:Value>
5: </s:Subcode>
6: </s:Code>
2、Fault Reason元素
對於一個SOAP Fault消息,除了必須有一個表示錯誤代碼的Code元素之外,還需要具有一個Reason元素用以表導致錯誤的原因。SOAP 1.2對Reason元素的格式作了如下的規范:
元素名稱必須為“SubCode”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”;
包含一個或者多個Text元素用以描述錯誤。
對於上面給出的SOAP Fault消息,具有如下一個SOAP Reason元素。Text元素中的lang屬性表示想相應的語言文化,也就是說,你可通過該屬性指定基於不同語言文化的文字用於描繪蘇錯誤的原因。
1: <s:Reason>
2: <s:Text xml:lang="zh-CN">被除數y不能為零!</s:Text>
3: </s:Reason>
3、Fault Node元素
由於在整個SOAP消息的路由過程中,錯誤可能發生在最終接收結點,也可能發生在中間結點。為了使SOAP Fault消息的接收者能夠判斷導致錯誤的SOAP結點類型,在生成Fault消息的時候,可以通過Node元素指定結點的類型。SOAP 1.2對Node元素作如下的規范:
元素名稱必須為“Node”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”;
元素值得類型為“xs:anyURI”,即通過URI表示的SOAP結點(參考SOAP報頭的Role屬性);
在上面給出的Fault消息中,我將Fault Node指定為http://http://www.artech.com/calculationcenter。
4、Fault Role元素
SOAP結點處理SOAP消息時候擔當著不同的角色。SOAP Fault的Role元素即用以表述導致錯誤的SOAP結點對應的角色。SOAP 1.2對Node元素的格式作了如下的規范:
元素名稱必須為“Role”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”;
元素值得類型為“xs:anyURI”,即通過URI表示的SOAP結點對於得角色(參考SOAP報頭的Role屬性)。
在上面給出的Fault消息中,我將Fault Node指定為http://http://www.artech.com/calculatorservice。
5、Fault Detail元素
在很多基於SOAP通信的應用中,SOAP Fault消息的接收者處理需要了解通過上面介紹的基本錯誤元素表示的錯誤信息之外,往往還需要一些對錯誤信息更加詳盡的描述。這樣的描述就可以通過Detail元素來表示。SOAP 1.2對Detail元素作了如下的規范:
元素名稱必須為“Detail”,命名空間名稱為“http://www.w3.org/2003/05/soap-envelope”
可以包含任意的XML元素,每個元素可以具有各自的命名空間
可以包含任意的XML屬性
通過上面給出的Fault消息,我們可以看出該元素對應著我們在第一節介紹的錯誤明細對象,既FaultException<TDetail>異常最終序列化生成Fault消息的時候,其Detail屬性表示的錯誤明細對象被序列化成Fault Detail元素。
1: <s:Detail>
2: <CalculationError xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com/">
3: <Message>被除數y不能為零!</Message>
4: <Operation>Divide</Operation>
5: </CalculationError>
6: </s:Detail>
二、 WCF下的異常:FaultException
在《WCF技術剖析(卷1)》中,我曾經提到過,在整個WCF體系下,數據存在的形態大體可以分為兩種:XML和托管對象(Managed Object)。WCF建立在.NET平台下,利用托管語言(C#和VB.NET)開發人員提供了一個面向對象的編程模型,所以,在WCF體系最頂層的數據形態表現為.NET托管對象。而最終服務調用體現在消息的交換上,消息時基於XML的(除了少部分非XML的消息,比如JSON)。從數據轉化的角度上講,WCF起到了一個將數據從這兩種形態數據進行轉化和適配的作用。
在WCF異常處理體系中,對於異常或者錯誤,在XML的世界裡最終通過Fault消息體現;而在托管對象的世界中,即使相應的Exception對象。上面以小節,我們在消息交換的角度對SOAP Fault進行了講解,接下來我們介紹的對象就是它在托管世界的對立體:FaultException。
通過千篇 一片文章的內容,我們知道了基於WCF異常處理的編程只要圍繞著FaultException這個類型來完成的,所以我們很有必要重新深入地認識這個對象。先通過下面的代碼片段來看看FaultException的基本定義:
1: [Serializable]
2: public class FaultException : CommunicationException
3: {
4: //其他成員
5: public FaultException();
6: public FaultException(FaultReason reason);
7: public FaultException(string reason);
8: public FaultException(FaultReason reason, FaultCode code);
9: public FaultException(string reason, FaultCode code);
10: public FaultException(FaultReason reason, FaultCode code, string action);
11: public FaultException(string reason, FaultCode code, string action);
12:
13: public string Action { get; }
14: public FaultCode Code { get; }
15: public override string Message { get; }
16: public FaultReason Reason { get; }
17: }
上面列出了FaultException的一些構造函數和公共屬性。Action表述最終生成到Fault消息中WS-Addressing報頭Action的值。而Code和Reason則對應著SOAP Fault中的Code和Reason元素,它們的類型分別為System.ServiceModel.FaultCode和System.ServiceModel.FaultReason,我們先來詳細地了解這兩種類型。
1、 System.ServiceModel.FaultCode
FaultCode表述錯誤的代碼,該代碼大體上可以看成是一種對錯誤的分類。在序列化FaultException對象生成Fault消息的時候,該對象最終會生成SOAP Fault的Code節點。在介紹SOAP Fault的時候,我們提到SOAP Fault中的Code是一種具有層級關系(Hierarchical)的結構,這也體現在FaultCode的定義上:從下面對FaultCode的定義代碼中 ,我們可以看到屬性SubCode的屬性是FaultCode本身。
1: public class FaultCode
2: {
3: public FaultCode(string name);
4: public FaultCode(string name, FaultCode subCode);
5: public FaultCode(string name, string ns);
6: public FaultCode(string name, string ns, FaultCode subCode);
7:
8: public static FaultCode CreateReceiverFaultCode(FaultCode subCode);
9: public static FaultCode CreateReceiverFaultCode(string name, string ns);
10: public static FaultCode CreateSenderFaultCode(FaultCode subCode);
11: public static FaultCode CreateSenderFaultCode(string name, string ns);
12:
13: public bool IsPredefinedFault { get; }
14: public bool IsReceiverFault { get; }
15: public bool IsSenderFault { get; }
16: public string Name { get; }
17: public string Namespace { get; }
18: public FaultCode SubCode { get; }
19: }
FaultCode的Name和NameSpace屬性表述SOAP Code中Value元素的值,而SubCode屬性則自然對應著同名的SubCode元素。IsPredefinedFault屬性表示該Fault Code是否屬於預定義的。WCF通過命名空間確定其是否是預定義的Fault Code,具體來講,只有具有以下三個命名空間的才屬於預定義的Fault Code:http://schemas.xmlsoap.org/soap/envelope/(SOAP 1.1)、http://www.w3.org/2003/05/soap-envelope(SOAP 1.2)和http://schemas.microsoft.com/ws/2005/05/envelope/none。對於名稱為Sender和Receiver的預定義Fault Code,它們的IsSenderFault和IsReceiverFault分別返回true。
FaultCode還定義了兩組靜態方法(CreateSenderFaultCode和CreateReceiverFaultCode)幫助我們方便地創建Sender和Receiver預定義FaultCode。比如,下面我們調用靜態方法CreateSenderFaultCode創建了一個FaultCode,該FaultCode的內容和我們前面給定的Fault消息中的Fault Code是一致的:
1: FaultCode code = FaultCode.CreateSenderFaultCode(new FaultCode("CalculationError", "http://www.artech.com/"));
對應在Fault消息中的Fault Code元素:
1: <s:Code>
2: <s:Value>s:Sender</s:Value>
3: <s:Subcode>
4: <s:Value xmlns:a="http://www.artech.com/">a:CalculationError</s:Value>
5: </s:Subcode>
6: </s:Code>
接下來,我們來介紹另一個類型System.ServiceModel.FaultReason。
2、 System.ServiceModel.FaultReason
FaultReason用於定於錯誤的原因,在SOAP Fault對應的元素為Reason,下面是其定義代碼:
1: public class FaultReason
2: {
3: public FaultReason(IEnumerable<FaultReasonText> translations);
4: public FaultReason(FaultReasonText translation);
5: public FaultReason(string text);
6:
7: public FaultReasonText GetMatchingTranslation();
8: public FaultReasonText GetMatchingTranslation(CultureInfo cultureInfo);
9: public override string ToString();
10:
11: public SynchronizedReadOnlyCollection<FaultReasonText> Translations { get; }
12: }
雖然SOAP Fault的Reason的值僅僅是一個字符文本,但是處於本地化(Localization)的支持,允許我們基於不同語言文化定義不同的內容。基於語言文化的Reason文本通過一個特殊的類型表示:FaultReasonText。
1: public class FaultReasonText
2: {
3: public FaultReasonText(string text);
4: public FaultReasonText(string text, CultureInfo cultureInfo);
5: public FaultReasonText(string text, string xmlLang);
6: public bool Matches(CultureInfo cultureInfo);
7:
8: public string Text { get; }
9: public string XmlLang { get; }
10: }
從上面對FaultReasonText的定義中可以看到,我們可以通過指定CultureInfo和String對象指定基於某種語言文化的Fault Reason。如果沒有顯式指定CultureInfo,默認采用的是當前線程的語言文化。
對於對FaultReason對象的構建,既可以通過指定一個FaultReasonText集合創建支持多語言文化的Fault Reason,也可以通過指定單個FaultReasonText創建基於某個單一語言文化的Fault Reason。最簡單的莫過於直接指定一個字符串表述的Reason文本,這是默認采用當前線程的語言文化。而屬性Translations返回一個FaultReasonText的集合。
下面的代碼中,我們創建了一個FaultReason對象對兩種語言文化提供支持(簡體中文和美式英語)
1: IList<FaultReasonText> reasonTexts = new List<FaultReasonText>();
2: reasonTexts.Add(new FaultReasonText("The input parameter is invalid!","en-US"));
3: reasonTexts.Add(new FaultReasonText("輸入參數不合法!", "zh-CN"));
4: FaultReason reason = new FaultReason(reasonTexts);
對應在SOAP Fault的Reason元素的內容為:
1: <s:Reason>
2: <s:Text xml:lang="en-US">The input parameter is invalid!</s:Text>
3: <s:Text xml:lang="zh-CN">輸入參數不合法!</s:Text>
4: </s:Reason>
FaultException進行提供過Code和Reason兩個基本SOAP Fault元素的定義,而不能提供對Detail元素的定義,因為該元素是通過另一個繼承自FaultException的異常類型定義的:FaultException<TDetail>。
3、 FaultException<TDetail>
當從服務端拋出異常時,如果需要通過一個對象用於描述錯誤的消息信息,不管該對的類型是基元類型(比如String,Int等)還是自定義類型(比如自定義數據契約),就不得不使用泛型的FaultException<TDetail>異常對象了。下面給出了FaultException<TDetail>的定義:
[Serializable]
public class FaultException
1: [Serializable]
2: public class FaultException<TDetail> : FaultException
3: {
4: //其他成員
5: public FaultException(TDetail detail);
6: public FaultException(TDetail detail, FaultReason reason);
7: public FaultException(TDetail detail, string reason);
8: protected FaultException(SerializationInfo info, StreamingContext context);
9: public FaultException(TDetail detail, FaultReason reason, FaultCode code);
10: public FaultException(TDetail detail, string reason, FaultCode code);
11: public FaultException(TDetail detail, FaultReason reason, FaultCode code, string action);
12: public FaultException(TDetail detail, string reason, FaultCode code, string action);
13:
14: public TDetail Detail { get; }
15: }
FaultException<TDetail>直接繼承自FaullException,泛型類型參數表示錯誤明細類型。通過相應的構造函數在創建FaultException<TDetail>對象的時候指定類型為TDetail的錯誤明細對象,該對象通過只讀屬性Detail獲取。錯誤明細類型必須是可序列化的,一般地,我們通將其定義成數據契約的形式。該類型通過FaultContractAttribute特性應用在服務契約相應的操作上面。
當WCF的服務端框架在進行錯誤提供過程中,將整個FaultException<TDetail>進行序列化並據此生成一個Fault消息,其Detail屬性表示的錯誤明細對象被序列化後的XML作為SOAP Fault的Detail元素。
到此為止,我們分別站在消息交換和編程的角度介紹了SOAP Fault和FaultException異常。在服務執行過程中,我們手工拋出FaultException異常,WCF服務端框架會對該異常對象進行序列化病最終生成Fault消息。當WCF客戶端框架介紹到該Fault消息之後,會做一項相反的操作:對Fault消息中進行解析和反序列化,重新生成並拋出FaultException異常。WCF框架自動為我們作了這麼多“幕後”工作,使得開發人員可以完全采用編寫一般的.NET應用程序的模式進行異常的處理:在錯誤的地方拋出相應異常,對於潛在出錯的方法調用進行相應的異常捕獲和處理。
所以,WCF的異常處理框架的核心功能就是實現FaultException異常和Fault消息之間的轉換,在[中篇]中,我們著重來討論這個話題。