首先以一個簡單的例子演示一下遠程調用發生異常的結果:
服務器端代碼如下:
[ServiceContract]
public interface IService1
{
[OperationContract]
void ErrorTest();
}
public class Service1 : IService1
{
public void ErrorTest() { throw new InvalidOperationException("異常測試");
}
該服務非常簡單,直接拋一個異常。當運行客戶端代碼觸發這個異常時,客戶端會接收到如下異常:
從中我們可以看到:雖然服務器端拋出的是InvalidOperationException異常,但是客戶端接收到的卻是FaultException異常。從WCF測試客戶端的傳遞的消息中也可以看出這一點。
WCF異常處理機制
-
通訊異常,這通常是因為鏈路的原因,比如服務沒有啟動,網絡阻塞等。通常是CommunicationException或者其派生類
-
超時異常,這類異常是因為操作時間超時,超時時會拋出 TimeoutException。
-
服務異常,由服務端根據具體的業務邏輯觸發,當業務邏輯拋出異常時,會將其封裝成FaultException拋給客戶端。
通信異常和超時異常是由WCF框架所產生的,由於網絡原因,它們出現是正常的,因此也被稱為預期異常,是正常調用的時候需要處理的異常。而FaultException異常則是業務邏輯中異常情況時出現的異常,是由自己實現的代碼中產生的,是非預期的異常。具體處理方式根據業務邏輯而定。
但值得注意的是:服務被設置為PerSession模式或者Single模式,服務異常還會導致服務對象被釋放而終止服務。(這個是在網上看到的說明,但我自己試的時候沒有掛,從WCF服務對象模型上來分析也不應該會掛,也沒有看到MSDN上哪兒有寫,待后續確認后再更新)
至於為什么要將CLR異常封裝成FaultException后拋出,我的理解是:對於異常信息,是序列化后才發布給客戶端的,也就是說,客戶端也得需要理解這個異常才能反序列化異常信息。因此,客戶端必須知道異常的格式,由於WCF的客戶端並非只有.net語言才能實現,因此需要發布異常格式,由於CLR的異常比較多,加上還有自定義異常,無法公布所有異常格式,因此,將其統一成FaultException發布,這樣客戶端才能順利解析。
了解了這個后,我們就知道InvalidOperationException異常為什么會變成了FaultException異常,但是存在的一個問題是:雖然客戶端能感知到調用服務發生了異常,但仍然不知道異常信息,有時無法進行進一步處理。
Debug期間異常信息傳遞
在前面的異常信息中就有說明:有關該錯誤的詳細信息,請打開服務器上的 IncludeExceptionDetailInFaults (從 ServiceBehaviorAttribute 或從 <serviceDebug> 配置行為)以便將異常信息發送回客戶端。它告訴了我們有兩個方式可以傳遞這個異常信息:
-
配置serviceBehaviors,將includeExceptionDetailInFaults設置為true
-
在服務上的ServiceBehaviorAttribute里面設置IncludeExceptionDetailInFaults
這兩種方式是等效的,不過建議在配置文件里面使用,統一設置比較方便,也方便統一關閉。
經過這個設置后,再運行客戶端代碼,這次就可以看到異常信息了:
連異常的調用棧也能看到,方便我們進行異常定位。
<StackTrace> 在 WcfService.Service1.ErrorTest() 位置 Service1.cs:行號24
在 SyncInvokeErrorTest(Object , Object[] , Object[] )
不過,這個在另一方面也暴露了服務器的信息,對於這種網絡程序來說是不安全的,因此只建議在Debug的時候使用,從serviceDebug的名字中也可以看出這一點。
構造FaultException
為了安全,前面所說的異常信息獲取方式,只能在Debug期間使用,那么,對於Release版本,則需要我們自己手動把運行異常封裝成FaultException返回。
public class Service1 : IService1
{
public void ErrorTest()
{
try
{
throw new InvalidOperationException("異常測試");
}
catch (Exception e)
{
throw new FaultException(e.Message);
}
}
}
這次我們就能獲取到異常信息了。
對於比較復雜的異常信息,可以自己構造一個FaultException<T>,如下是一個簡單的示例:
[ServiceContract]
public interface Iservice
{
[OperationContract]
[FaultContract(typeof(DataAccessFault))]
void Operation();
}
public class Service : Iservice
{
public void Operation()
{
try
{
// Access database …
}
catch (DbException e)
{
DataAccessFault fault = new DataAccessFault();
fault.AdditionalDetails = e.Message;
throw new FaultException<DataAccessFault>(fault);
}
}
}
自己手動構造FaultException的方式比較麻煩,需要在每一個提供的接口中都捕獲異常,並重新封裝拋出,一來不好看,處理起來也比較麻煩。由於篇幅有限,下篇文章中介紹一個更為簡單的方法。