程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF技術剖析之二十四:ServiceDebugBehavior服務行為是如何實現異常的傳播的?

WCF技術剖析之二十四:ServiceDebugBehavior服務行為是如何實現異常的傳播的?

編輯:關於.NET

服務端只有拋出FaultException異常才能被正常地序列化成Fault消息,並實現向客戶端傳播。對於一般的異常(比如執行Divide操作拋出的DivideByZeroException),在默認的情況下,異常信息無法實現向客戶端傳遞。但是,倘若為某個服務應用了ServiceDebugBehavior這麼一個服務行為,並開啟了IncludeExceptionDetailInFaults開關,異常信息將會原封不動地傳播到客戶端。WCF內部是如何處理拋出的非FaultException異常的呢?

實際上,WCF對非FaultException異常的處理並不復雜,我們現在就來簡單介紹一下相關的流程:在執行服務操作過程中,如果拋出一個非FaultException異常,WCF會先判斷IncludeExceptionDetailInFaults開發是否開啟,如果沒有,WCF會手工創建一個MessageFault對象,並根據當前線程的語言文化從資源文件中獲取一段固定的文本作為MessageFault的FaultReason(就是我們在《WCF基本的異常處理模式[上篇]》的例子中看到的那段文字)。此外,固定的FaultCode被創建出來作為該MessageFault的Code。最終,WCF將該MessageFault轉換成一個Fault消息,並采用固定的Action作為該消息的Action報頭。所以,無論服務端拋出怎樣的異常,客戶端捕獲的總是具有相同信息的FaultException異常。

注:客戶端的錯誤信息總是這麼一段文字:“由於內部錯誤,服務器無法處理該請求。有關該錯誤的詳細信息,請打開服務器上的 IncludeExceptionDetailInFaults (從 ServiceBehaviorAttribute 或從 <serviceDebug> 配置行為)以便將異常信息發送回客戶端,或在打開每個 Microsoft .NET Framework 3.0 SDK 文檔的跟蹤的同時檢查服務器跟蹤日志。”

上面說的是IncludeExceptionDetailInFaults開關關閉的情況。如果IncludeExceptionDetailInFaults開啟,WCF則會基於該異常對象創建ExceptionDetail對象,並將該對象作為明細對象創建MessageFault(采用固定FaultCode)。最終,將此MessageFault轉換生成Fault消息,當然Action也是采用固定的預定值。因此,在這種情況下,服務端拋出的信息總是能夠原封不動地傳遞到客戶端。而客戶端捕獲的總是一個泛型的FaultException<ExceptionDetail>異常。

一、ExceptionDetail對象為何能被反序列化?

對於異常對象的序列化和反序列化工作,最終都回落在FaultFormatter這麼一個對象上(具體原理,可以參考《深入剖析WCF底層異常處理框架實現原理[中篇]》)。無論是虛列化還是反序列化,都具有一個根本的前提:確定對象的類型。FaultFormatter依賴創建時指定的一個FaultContractInfo列表來獲知具體的類型,而該列表最初來源於應用在操作方法上FaultContractAttribute特性的定義。

那麼,對於應用了ServiceDebugBehavior服務行為,並開啟了IncludeExceptionDetailInFaults的場景,客戶端如何能夠把承載與Fault消息中的表示錯誤明細的XML片段通過反序列化生成ExceptionDetail對象的呢?由於我們不曾通過FaultContractAttribute特性將ExceptionDetail類型應用在相應的操作方法上面,FaultFormatter無法確定反序列化對象的類型,照理說反序列化是無法成功的,這是為何呢?

其實原因很簡單,WCF在初始化FaultFormatter的時候會基於ExceptionDetail類型創建FaultContractInfo對象,並將其添加到屬於自己的FaultContractInfo列表中。相應的實現基本上可以通過下面的偽代碼體現:

   1: internal class FaultFormatter : IClientFaultFormatter, IDispatchFaultFormatter
2: {
3: //其他成員
4: private FaultContractInfo[] faultContractInfos;
5: internal FaultFormatter(SynchronizedCollection<FaultContractInfo> faultContractInfoCollection)
6: {
7: List<FaultContractInfo> list= new List<FaultContractInfo>(faultContractInfoCollection);
8: faultContractInfoCollection.Add(new FaultContractInfo("http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault", typeof(ExceptionDetail)));
9: this.faultContractInfos = list.ToArray();
10: }
11: }

二、如果直接拋出FaultException<ExceptionDetail>異常呢?

既然FaultFormatter能夠自動實現基於ExceptionDetail對象的序列化和反序列化,那麼就意味著我們可以在具體的服務操作中直接拋出FaultException<ExceptionDetail>異常,而無須再將ExceptionDetail作為錯誤契約類型通過FaultContractAttribute特性應用到相應的服務操作上面了。同樣以我們的計算服務為例,在Divide方法中我們直接用ExceptionDetail封裝在運算過程中拋出的異常,最終拋出FaultException<ExceptionDetail>異常。

   1: using System.ServiceModel;
2: using Artech.WcfServices.Contracts;
3: using System;
4: namespace Artech.WcfServices.Services
5: {
6: [ServiceBehavior(Namespace="http://www.artech.com/")]
7: public class CalculatorService : ICalculator
8: {
9: public int Divide(int x, int y)
10: {
11: try
12: {
13: return x / y;
14: }
15: catch (Exception ex)
16: {
17: throw new FaultException<ExceptionDetail>(new ExceptionDetail(ex), ex.Message);
18: }
19: }
20: }
21: }

在客戶端,我們就可以直接捕獲FaultException<ExceptionDetail>異常了。下面的代碼中,我們將捕獲的FaultException<ExceptionDetail>異常相關的信息打印出來:

   1: using System;
2: using System.ServiceModel;
3: using Artech.WcfServices.Contracts;
4: namespace Artech.WcfServices.Clients
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(
11: "calculatorservice"))
12: {
13: ICalculator calculator = channelFactory.CreateChannel();
14: using (calculator as IDisposable)
15: {
16: try
17: {
18: int result = calculator.Divide(1, 0);
19: }
20: catch (FaultException<ExceptionDetail> ex)
21: {
22: Console.WriteLine("Action: {0}", ex.Action);
23: Console.WriteLine("Code: {0}:{1}", ex.Code.Namespace,ex.Code.Name);
24: Console.WriteLine("Detail");
25: Console.WriteLine("\tMessage: {0}", ex.Detail.Message);
26: Console.WriteLine("\tType: {0}", ex.Detail.Type);
27: }
28: }
29: }
30: Console.Read();
31: }
32: }
33: }

輸出結果(注意Action正好和在FaultFormatter中指定的值是一致的):

   1: Action: http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault
2: Code:http://www.w3.org/2003/05/soap-envelope:Sender
3: Detail:
4: Message:試圖除以零。
5: Type:System.DivideByZeroException
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved