服務須要向客戶端報告特定錯誤,當WCF默認的錯誤屏蔽方法並不包含這一實現。另外一個重要的問題與傳播到客戶端有關,即因爲異常是針對特定技術的,所以沒法跨越服務邊界而被共享。要實現無縫的互操做性,就須要將基於特定技術的異常映射爲某種與平臺無關的錯誤信息。這種表現形式就是所謂的SOAP錯誤。編程
SOAP錯誤基於一種行業標準,它不依賴任何一種諸如CLR異常、Java異常或者C++異常等的特定技術異常。安全
若要返回一個SOAP錯誤,服務就不能拋出一個傳統的CLR異常。相反,服務必須拋出FaultException<T>類的實例,以下所示:服務器
// 表示 SOAP 錯誤。 [Serializable] public class FaultException : CommunicationException { public FaultException(); public FaultException(FaultReason reason); public FaultException(string reason); // 返回 System.ServiceModel.Channels.MessageFault 對象。 public virtual MessageFault CreateMessageFault(); // 更多成員 } // 用於在客戶端應用程序中捕獲經過協定方式指定的 SOAP 錯誤。 [Serializable] public class FaultException<TDetail> : FaultException { public FaultException(TDetail detail); public FaultException(TDetail detail, FaultReason reason); public FaultException(TDetail detail, string reason); // 更多成員 }FaultException<T>是FaultException的特殊化,所以任何針對FaultException異常進行編程的客戶端都能處理FaultException<T>類型。FaultException繼承子CommunicationException,所以,客戶端能夠在單個catch裏捕獲全部的通訊和服務端異常。ide
FaultException<T>的類型參數T負責傳遞錯誤細節。不要求錯誤細節的必須爲Exception的派生類,它能夠是任何類型。惟一的約束是該類型必須標記爲可序列化或數據契約。函數
以下代碼,展現了一個簡單的計算器服務,在實現Divide()方法,若是除數爲0,則拋出一個FaultException<DivideByZeroException>異常。spa
[ServiceContract] interface IMyContract { [OperationContract] double Divide(double number1,double number2); } class MyService : IMyContract { public double Divide(double number1,double number2) { if(number2 == 0) { DivideByZeroException exception = new DivideByZeroException(); throw new FaultException<DivideByZeroException>(exception); } return number1 / number2 } }除了FaultException<DivideByZeroException>異常,服務還可以拋出參數不能Exception派生類型的異常。代理
throw new FaultException<double>(number2);將Exception派生類做爲錯誤細節類型更加符合傳統.NET編程,代碼也具備更強的可讀性。code
傳遞給FaultException<T>構造函數的reason參數爲異常消息,所以能夠傳遞字符串做爲reason參數的值。對象
DivideByZeroException exception = new DivideByZeroException("number2 is 0"); throw new FaultException<DivideByZeroException>(exception,"Reason:" + exception.Message);
默認狀況下,服務拋出傳遞到客戶端的異常均爲FaultException類型,即便在服務器拋出FaultException<T>的狀況下也是如此。其緣由在與服務但願與客戶端共享的基於錯誤之上的任何異常都必須屬於服務契約行爲的一部分,從而使得服務可以通知WCF它但願可以穿透錯誤的屏蔽。爲此,WCF提供了錯誤契約,經過它能夠列出服務可以拋出的錯誤類型。這些錯誤類型的類型參數應該與FaultException<T>使用的類型參數相同。只是他們在錯誤契約中列出,WCF客戶端就可以分辨契約錯誤與其它錯誤之間的區別。blog
服務可使用FaultContractAttribute特性定義它的錯誤契約:
[ServiceContract] interface IMyContract { [OperationContract] [FaultContract(typeof(DivideByZeroException))] double Divide(double number1,double number2); }FaultContract特性只對標記了它的方法有效。也就是說,只有這樣的方法才能拋出錯誤,並將它傳遞給客戶端。
此外,若是操做拋出的異常沒有包含在契約中,它就會以普通的FaultException形式傳遞給客戶端。爲了傳遞異常,服務拋出與錯誤契約所列徹底相同的細節類型。例如,若要拋出以下的錯誤契約定義:
[FaultContract(typeof(DivideByZeroException))]
服務就必須拋出一個FaultException<DivideByZeroException>異常,即便是父類--子類也不行,必須是徹底相同的類型。
FaultContract特性支持重複配置,能夠在單個操做中列出多個契約。
[ServiceContract] interface IMyContract { [OperationContract] [FaultContract(typeof(DivideByZeroException))] [FaultContract(typeof(InvalidOpreationException))] double Divide(double number1,double number2); }咱們不能爲單向操做提供錯誤契約。由於,從理論上講,單向操做是沒有返回值的。
錯誤處理
錯誤契約與其它服務元數據一同發佈。當WCF客戶端導入該元數據時,契約定義包含了錯誤契約及錯誤細節類型的定義。錯誤細節類型的定義包含了相關的數據契約。若是細節類型是某個包含了各類專門字段的定製異常,那麼錯誤細節類型對數據契約的支持就顯得格外重要了。
客戶端指望可以捕獲和導入的錯誤類型。以下所示:
MyContractClient proxy = new MyContractClient(); try { proxy.Divide(2,0); proxy.Close(); } catch(FaultException<DivideByZeroException> exception) { .... } catch(FaultException exception) { ...... } catch(Exception exception) { ...... }注意,客戶端仍然可能引起通訊異常或者服務拋出的其它異常。
客戶端能夠採用FaultException基類異常的方式贊成地處理全部與通訊無關的全部異常。
MyContractClient proxy = new MyContractClient(); try { proxy.Divide(2,0); proxy.Close(); } catch(FaultException exception) { .... } catch(CommunicationException exception) { ...... }錯誤與通道
若是要作到能夠預料錯誤,則須要在錯誤契約中列出一個能夠拋出的錯誤。所以,當服務器拋出的錯誤屬於服務錯誤契約中列出的異常時,就不會致使通訊通道出現錯誤。客戶端可以捕獲該異常,繼續使用代理或者安全的關閉代理。這就容許服務類將常規異常和列舉在錯誤契約中的錯誤區別對待,讓這些錯誤不會致使通道出現故障。這一能力不限於服務,若是服務調用的任意下游的.NET拋出這樣的錯誤,也不會讓鏈接客戶端的通道出現故障。
因爲通訊異常或回調自身拋出的異常,到客戶端的回調天然就會失敗。與服務契約操做相似,回調契約操做一樣能夠定義錯誤契約,以下所示:
[ServiceContract(CallbackContract = typeof(IMyContractCallback))] interface IMyContract { [OperationContract] void DoSomething(); } interface IMyContractCallback { [OperationContract] [FaultContract(typeof(int))] [FaultContract(typeof(DivideByZeroException))] void OnCallBack(); }當服務在一個服務操做中調用回調時,若是返回到它正在調用的客戶端,且異常既不是FaultException也不是它的子類,則狀況就變得異常複雜了。因爲雙向通訊的全部綁定都維持了一個傳輸會話層,所以在回調期間,異常會終止從客戶端到服務的傳輸。
因爲TCP和IPC綁定不管從客戶端到服務發起的調用,仍是服務到客戶端的回調,都使用了相同的傳輸,一旦回調拋出異常,則第一個調用服務的客戶端即便服務已經捕獲了異常也會即刻收到一個CommunicationException異常。這就是在兩個方向重用了相同傳輸帶來的後果,致使回調傳輸出現錯誤(等同於讓客戶端到服務傳輸出現錯誤)。服務可以捕獲和處理異常,可是客戶端仍然會得到它的異常:
[ServiceContract(CallbackContract = typeof(IMyContractCallback))] interface IMyContract { [OperationContract] void DoSomething(); } interface IMyContractCallback { [OperationContract] [FaultContract(typeof(int))] [FaultContract(typeof(DivideByZeroException))] void OnCallBack(); } [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class MyService : IMyContract { public void DoSomething() { IMyContractCallback callback = OperationContext.Current.GetCallbackChannel<IMyContractCallback>(); try { callback.OnCallBack(); } catch(FaultException<DivideByZeroException> exception) //客戶端依然可以得到異常 { Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message); } catch(FaultException<int> exception) { Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message); } catch(CommunicationException exception) { Trace.WriteLine("Callback threw: " + exception.GetType() + " " + exception.Message); } } }