任何程序都離不開對異常的處理,良好的異常處理方式可加快尋找出異常的根源,同時也需要避免暴露敏感信息到異常中。WCF這種典型的服務端和客戶端交互的程序,服務端的異常更需要適當的處理。下面以一個簡單的服務為例,說明WCF中處理異常的方式。
WCF服務定義如下,很明顯方法Divide在divisor為0的時候將會拋出異常
View Code
{
public int Divide( int dividend, int divisor)
{
return dividend / divisor;
}
public int Add( int a, int b)
{
return a + b;
}
}
客戶端調用如下:
View Code
{
try
{
Console.WriteLine(client.Divide( 20, 0));
}
catch (FaultException ex)
{
Console.WriteLine(ex.Reason);
}
}
首先需要知道的是,WCF的異常信息默認是以FaultException的形式返回到客戶端,FaultException的關鍵屬性Reason是對客戶端反饋的最重要信息之一。以上客戶端代碼調用之后,默認的FaultException返回的Message信息如下:
由於內部錯誤,服務器無法處理該請求。有關該錯誤的詳細信息,請打開服務器上的 IncludeExceptionDetailInFaults (從 ServiceBehaviorAttribute 或從 <serviceDebug> 配置行為)以便將異常信息發送回客戶端,或在打開每個 Microsoft .NET Framework 3.0 SDK 文檔的跟蹤的同時檢查服務器跟蹤日志。
根據異常的提示,意思說如果要在客戶端看到詳細的Exception信息,那么請將ServiceBehavior對應的IncludeExceptionDetailInFaults屬性設置為True,通常在配置中表現為如下設置:
View Code
2 <behavior>
3 <serviceMetadata httpGetEnabled= " True " httpGetUrl= " http://localhost:8733/CalculateService/ "/>
4 <serviceDebug includeExceptionDetailInFaults= "True " />
5 </behavior>
6 </serviceBehaviors>
通過以上設置之后,客戶端輸出的內容為“嘗試除以零”,這個提示信息跟原始的異常信息是一致,即返回的FaultException中的Reason包含原始異常的Message的值,但是這樣處理之后服務端所報出的異常信息直接傳到了客戶端,比如一些保密信息也可能輸出到了客戶端,因此對於異常信息必須進行一個封裝。最直接的形式莫過於在服務端就把異常給捕獲了,並重新throw一個FaultException
服務端的代碼改進如下,經過以下改進,那么客戶端得到的信息僅僅是"操作失敗",同時服務端也記錄了異常信息(這時IncludeExceptionDetailInFaults是設置為False的)。
View Code
2 {
3 return dividend / divisor;
4 }
5 catch (Exception ex)
6 {
7 Console.WriteLine(ex.Message);
8 throw new FaultException( " 操作失敗 ");
9 }
當然這是FaultException的默認用法,FaultException還支持強類型的異常錯誤信息,返回更加豐富和精確的錯誤提示。假設定義如下通用的一個FaultContract類型,將出錯時的用戶名和線程名字記錄到異常信息中,因為異常信息也是通過SOAP格式傳輸的,因此跟定義其他DataContract的方式一樣。
CommonFaultContract
2 public class CommonFaultContract
3 {
4 [DataMember]
5 public string UserName { get; set; }
6 [DataMember]
7 public string ThreadName { get; set; }
8 }
那么服務方法的接口需要增加如下標記,如果不這樣標記,那么客戶端得到的異常類型依然是FaultException,而不是強類型的異常信息。
[FaultContract(typeof(CommonFaultContract))]
int Divide(int dividend, int divisor)
實現方法中拋出異常的部分代碼改成如下:
異常處理
2 {
3 Console.WriteLine(ex.Message);
4 throw new FaultException<CommonFaultContract>( new CommonFaultContract
5 {
6 UserName = Environment.UserName,
7 ThreadName = System.Threading.Thread.CurrentThread.Name
8 }, " 操作失敗 ");
9 }
這時候重新生成客戶端的代理類,然后更新客戶端的代碼如下,紅色部分即獲取強類型的異常錯誤信息。
View Code
2 {
3 Console.WriteLine(client.Divide( 20, 0));
4 }
5 catch (FaultException<CommonFaultContract> ex)
6 {
7 Console.WriteLine(ex.Detail.ThreadName);
8 Console.WriteLine(ex.Detail.UserName);
9 Console.WriteLine(ex.Reason);
10 }
當然在具體應用中還需要根據需求,返回不同的信息,構建不同的FaultContract。
以上服務端捕獲的異常方法,適用於方法比較少的情況,如果有十多個方法,一個個去寫try catch然后做標記等,那么工作量會很大,而且代碼也不利於重用。嘗試尋找像MVC Controller那樣的統一處理Exception的方式,將異常處理都放在基類中,那么只要繼承與這個基類的方法都不需要去寫try catch去捕獲異常。但WCF中似乎沒有這樣的機制,放棄了這種做法。
最近在研究Enterprise Lib中對WCF的支持時,發現Exception Block中還特地有針對WCF程序異常處理的解決方案,而且滿足以上說道的需求,即可記錄異常,又可對異常信息進行封裝。更重要的時,自動處理運行時的異常信息,不需要挨個方法的去寫Try catch。秉承企業庫的優秀傳統,大部分工作還是通過配置就可以完成了,非常好的解決方案。下面介紹具體的使用步驟。
步驟一:
引用以下dll
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF.dll
Microsoft.Practices.EnterpriseLibrary.Common.dll
Microsoft.Practices.ObjectBuilder2.dll
步驟2:
在具體的實現類中,增加如下屬性標記,其中WcfException為企業庫中Exception Block中的一個異常處理策略,具體如何配置異常處理策略,請參考企業庫的幫助文檔。
[ExceptionShielding("WcfException")]
public class CalculateService : ICalculateService
那么只要增加了[ExceptionShielding("WcfException")]這個屬性標記之后,所有運行時的異常都將交給策略名為WcfException的異常處理block來處理,在這里就可以執行一些異常記錄以及異常封裝的操作。
步驟3:
將異常信息封裝為FaultException,這個動作也是通過配置來完成。在Exception節點中添加一個Fault Contract Exception Handler。
Fault Contract Exception Handler需要設置以下兩個屬性值
exceptionMessage:所有異常封裝后的錯誤信息
faultContractType:即返回異常的faltContract類型,這個類型必須指定一個,哪怕方法中沒有用到也要,如果方法中有用到,那么客戶端那邊就能得到強類型FaultException,否則就是普通的FaultException。這里指定為之前定義的CommonFaultContract。
對於faultContract類型的值,還可以通過PropertyMappings來自定義需要從原始異常信息中映射到faultContract的屬性中,這個屬性可選。
經過以上步驟配置之后,服務端的程序就具備了自動處理異常的功能。客戶端還是跟往常那樣調用,不過具體是用FaultException捕獲異常還是FaultException<T>去捕獲異常,還得根據定義方法中是否標記了FaultContract。之后若定義了其他服務接口,同樣也僅僅需要在實現類上加上[ExceptionShielding("WcfException")]標記即可。
(圖片后續補上)
