我們可以這樣來簡單地描述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 DispatchOpe
ration
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異常對象並將其拋出來