概述:本節主要講述在服務呼叫中可能發生的異常及模擬異常的發生,並分析何時可捕獲何種異常,以及如何把服務異常以正確的方式傳遞到客戶端,
文章最後給出正確捕獲異常的捕獲順序。這次異常捕獲僅為介紹,部分為應用性功能,所以程式碼和行文相對簡單,也介紹了在伺服器端異常處理的一些技巧。
1、 首先,我們建立一個簡單的計算器伺服器和客戶端,如下:
點擊展開程式碼
//伺服器[ServiceContract]public interface ICalc{[OperationContract][FaultContract(typeof(GreentingError))]string Div(int x, int y);}public class Calc : ServiceBase, ICalc {public string Div(int x, int y ) {string result = string.Empty; try {result = string.Format("result: {0}", x / y); } catch (DivideByZeroException ex) {throw ex; }return result; }}//客戶端[ServiceContract] public interface ICalc { [OperationContract] [FaultContract(typeof(GreentingError))] string Div(int x, int y); }public class CalcClient : ClientBase<ICalc>, ICalc{ public string Div(int x, int y ) {return base.Channel.Div(x, y); }}
好吧,我承認程式碼相當的簡單,不過我喜歡簡潔的東西。
2、 簡單的東西就是好,呼叫都簡單很多;我們來調用一下。
try { CalcClientcalcclient = new CalcClient(); string result =calcclient.Div(10, 0); Console.WriteLine(result); Console.ReadKey(); } catch (TimeoutExceptionex) {throw ex; ex) {throw ex; } catch (FaultExceptionex) {throw ex; catch (System.ServiceModel.CommunicationException ex) {throw ex; } catch (Exceptionex) {throw ex; }
3.當我們在呼叫服務的Div(int x,int y)方法並給對數y傳遞了值為0後,伺服器端將會引發DivideByZeroException的異常,這在預料之中。這時候,
在客戶端的FaultException部分捕獲了這個異常。
4.沒問題,我們再在伺服器程式碼中手動拋出FaultException異常。
catch (DivideByZeroException ex){FaultException exception = new FaultException(ex.Message); throw exception;}
這時候發現,還是FaultException捕捉了這個異常,為何?
5.再做一個測試。
在服務加入這句程式碼:System.Threading.Thread.Sleep(70000);使得服務逾時。
這回終於是TimeOutException捕獲了伺服器的異常,那麼我們就要問了,FaultException< GreentingError>何時會捕獲異常呢?答案是當伺服器拋出FaultException< GreentingError>的時候,引用MSDN上的一段話(綠色部分):
如果偵聽器接收到操作協定中不期望或未指定的SOAP 錯誤,將會引發FaultException物件, 可以傳送兩種類型的SOAP 錯誤:已宣告的和未宣告的。 已宣告的SOAP 錯誤是指其中的某個操作具有System.ServiceModel.FaultContractAttribute屬性(用於指定自訂SOAP 錯誤類型)的錯誤。 未聲明的SOAP 錯誤是在操作的協定中沒有指定的錯誤。這裡的「不期望或未指定的SOAP 錯誤」是指未在服務作業中套用FaultContractAttribute包裝的自訂錯誤類型。
6.那麼何時會捕獲CommincationException異常呢?
MSDN上說是:應用程式處理在通訊期間可能會引發的CommunicationException 對象
好吧,為了引發這個異常,我們來作如下操作。首先在伺服器關閉目前通道物件。
OperationContext.Current.Channel.Close();
很遺憾,客戶端並沒有捕獲到CommunicationException,而是捕獲了TimeOutException異常!因為服務通道關閉後,並未發生異常,所以沒有回傳訊息到客戶端,客戶端在等待一定時間後,逾時退出。
所以我們在關閉頻道的同時指定一個TimeSpan。這樣可以讓呼叫立即返回,當然,也可以透過Channel.Abort來完成呼叫返回。
OperationContext.Current.Channel.Close(new TimeSpan(5000));
在呼叫了IContextChannel的Close方法的同時,指定在超時前必須完成發送操作的時間,這樣可以使得訊息在指定時間內立即返回,而不必等到服務調用超時,否則到時客戶端必將引發TimeOutException異常,而不是CommunicationException異常。
7.補救措施
同時,為了在服務出現異常時我們可以採取一些補救的措施,我們新建了一個抽象類別ServiceBase,並使得Calc服務實作類別繼承自它,這樣我們就可以在服務各種狀態轉換中取得控制權。 ServiceBase類別如下:
public abstract partial class ServiceBase { private IContextChannel channel = null; protected ServiceBase() { channel = OperationContext.Current.Channel; channel.Opening += new EventHandler(delegate(object sender, EventEventArgs e*/* } ) ; channel.Opened += new EventHandler(delegate(object sender, EventArgs e) {/* TO DO*/ }); channel.Closing += new EventHandler(delegate(object sender, EventArgs e) {/* TO 1DO*/ }); channel.Closed += new EventHandler(delegate(object sender, EventArgs e) { Abort(); }); channel.Faulted += new EventHandler(delegate(object sender, EventArgs e) { Abort(); }) ; } void Open() {/* TO DO*/ } void Close() { /* TO DO*/} void Abort() { channel.Abort(); }}
從上面的程式碼可以看出,在服務通道關閉以後,我們立即將服務中止,讓訊息立即返回,這時候即使在操作中關閉了服務而又未指定超時完成的時間,呼叫依然可以立即返回。
這次客戶端總算捕獲了CommunicationException異常,請見下圖:
為何會這樣?
8.讓我們來看CommunicationException的繼承層次,從中我們可以得到啟示。
8.1、首先是FaultException<TDetail>的繼承層次。
8.2、再次是TimeOutException的繼承層次。
9.從上圖可以看出,TimeOutException和CommunicationException都繼承自SystemException類,而FaultException繼承自CommunicationException,最後是FaultException<TDetail>繼承自FaultException類別。
10、最後我們得出,在客戶端正確的捕獲異常的順序應該是:
TimeOutException> FaultException<TDetail> > FaultException >CommunicationException > Exception。在這裡強烈建議開發人員拋出和捕獲FaultException<TDetail>類型的例外。
作者:老米來源: http://www.cnblogs.com/viter/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。