從FaultContractAttribute的定義我們可以看出,該特性可以在同一個目标對象上面多次應用(AllowMultiple = true)。這也很好了解:對于同一個服務操作,可能具有不同的異常場景,在不同的情況下,需要抛出不同的異常。
但是,如果你在同一個操作方法上面應用了多了FaultContractAttribute特性的時候,需要遵循一系列的規則,我們現在就來逐條介紹它們。
一、多次聲明相同的錯誤明細類型
比如在下面的代碼中,對于操作Divide,通過FaultContractAttribute特性對同一個錯誤明細類型CalculationError進行了兩次設定。
WCF服務端架構在初始化ServiceHost,并建立服務表述的時候(關于服務描述,以及在服務寄宿過程中對服務描述的建立,《WCF技術剖析(卷1)》的第7章有詳細的介紹),會抛出如圖1所示的InvalidOperationException異常。

圖1 多次聲明相同的錯誤明細類型導緻的異常
但是,如果你在應用FaultContractAttribute特性指定相同錯誤明細類型的同時,指定不同的Name或者Namespace,這是允許的。比如下面的代碼中,在兩個FaultContractAttribute特性中,同樣是指定的相同的錯誤明細類型CalculationError,由于我們為之指定了不同的Name,在寄宿服務的時候将不會有上述異常的發生。
二、多次聲明不同的具有相同有效名稱錯誤明細類型
多次聲明的錯誤類型的類型雖然不同,但是如果我們為其指定相同的Name和Namespace我們可以将Name和Namespace的組合稱為有效名稱QN:Qualified Name),這依然是不允許的。比如下面的代碼中,通過FaultContractAttribute特性為Divide操作指定了兩個不同的錯誤明細類型(CalculationError和CalculationException),但是設定的名稱卻是相同的(CalculationError)。
對于這種情況,在服務寄宿的時候,依然會和上面一樣抛出一個InvalidOperationExcepiton異常,如圖2所示:
圖2 多次申明具有相同有效名稱導緻的異常
三、多次聲明不同的具有相同資料契約有效名稱的錯誤明細類型
還有另一種情況:雖然是多次申明的是不同的錯誤明細類型,但是通過DataContractAttribute特性定義它們的時候,指定了相同的名稱和命名空間。如果我們将它們通過FaultContractAttribute特性應用到同一個操作上面,又會出現怎樣的問題了。比如,在下面的代碼中,我們定義了兩個不同錯誤明細類型(CalculationError和CalculationFault),它們具有相同的資料契約名稱(CalculationError)和命名空間(http://www.artech.com/)。
如果我們通過下面的方式通過FaultContractAttribute特性将這兩個類型應用到同一個服務操作上面,服務寄宿不會出什麼問題,用戶端的方法調用也能正常運作。
但是,當我們試圖通過HTTP-GET或者标準的MEX終結點擷取以WSDL表示的服務中繼資料(Metadata)的時候就會出現問題。至于為什麼會導緻這樣的問題,你大體可以這樣來了解:當WCF為某個操作的錯誤描述(Fault Description)的時候,會建立一個字典來存儲通過FaultContractAttribute特性指定的基于錯誤明細類型的資料契約。對于這個字典來說,它的Key為資料契約的有效名稱(QN:Qualified Name),即名稱和命名空間組合。由于CalculationError和CalculationFault具有相同的名稱和命名空間,這無疑會造成Key的沖突。
由于資料契約是使對資料結構的一種描述,如果兩個資料契約時等效的,不管其具體的托管類型是什麼,WCF在遇到上述情況的時候,會自動識别并忽略其中一個,進而保證中繼資料能夠正确産生。比如說,如果我們将CalculationFault進行如下的改寫,服務的中繼資料就能夠被正常地獲得了。
四、通過XmlSerializer對錯誤明細對象進行序列化
對于任何分别是架構來說,序列化和反序列化都是其功能體系中重要的一環。WCF通過一個重要的對象實作對托管對象的序列化和反序列化:序列化器(Serializer)。具體來說,所有序列化和反序列化的功能又最終落實到兩個具體的序列化器上:DataContractSerializer和XmlSerializer。關于這兩種序列化器,在《WCF技術剖析(卷1)》第5章中已經有過深入的探讨,在這裡就需要在畫蛇添足了。
WCF采用的預設序列化器是DataContractSerializer,但是有的時候,我們需要顯示地控制某個服務或者服務的某個操作的序列化行為,通過XmlSerializer來序列化和反序列化操作的參數對象和傳回值。舉個例子,一個服務的絕大部分操作的參數類型都是通過資料契約的方式定義,但是對于個别的操作參數類型依然沿用的是傳統XML的定義方式。在這種情況下,我們希望的是專門對這幾個操作進行定制,讓它們采用XmlSerializer作為它們的序列化器。
我們可以通過對自定義特性System.ServiceModel.XmlSerializerFormatAttribute的應用幫助我們是相上面的功能。從先面對XmlSerializerFormatAttribute的定義我們可以看出:應用特性的目标元素的類型包括接口、類和方法。也就是說,XmlSerializerFormatAttribute既可以應用于服務契約接口上,也可以應用于服務類型上,甚至可以應用于服務接口和服務類型的方法上。
在預設的情況下,XmlSerializerFormatAttribute特性僅僅控制操作的參數和傳回值的序列化行為,而不能控制錯誤明細對象的序列化行為。也就是說,基于在某個操作方法上應用了XmlSerializerFormatAttribute特性,WCF會采用XmlSerializer作為所有參數和傳回值的序列化器,對于出現異常指定的錯誤明細對象,依然采用預設的DataContractSerializer進行序列化和反序列化。我們可以通過SupportFaults屬性來顯式地選擇XmlSerializer作為錯誤明細對象的序列化器。在下面的代碼中,我們将XmlSerializerFormatAttribute特性應用在服務契約的Divide操作上面,并将SupportFaults屬性設為true。
那麼對于Divide操作,WCF将會采用XmlSerializer同時作為參數、傳回值和錯誤明細對象的序列化器。比如在這個時候,我們采用下面的形式對CalculationError進行重新定義:
在被零除而抛出異常的情況下,WCF将會生成如下一個Fault SOAP,其中s:Body>// 節點中的XML為CalculationError對象序列化所的。