閱讀904 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Enterprise Library深入解析與靈活應用(8):WCF與Exception Handling AppBlock集成[下]

上篇中,我詳細介紹了如何通過自定義ClientMessageInspector和ErrorHandler,實現WCF與微軟企業庫中的Exception Handling Application Block(EHAB)之間的集成。這個方案的基本思路就是:當異常從服務端拋出,利用EHAB針對某個配置好的異常處理策略進行處理;然後將處理有的異常通過ServiceExceptionDetail對象進行封裝,最後序列化置於Fault消息,最終被返回給客戶端;客戶端接收到該Fault消息後,提取並創建ServiceExceptionDetail對象,並通過反射重建異常;最後將異常拋出,使客戶端可以根據客戶端配置的異常處理策略對該異常進行進一步的處理。(Source Code從這裏下載)

為了實現WCF對ServiceExceptionDetail對象的序列化和反序列化,我們必須通過FaultContractAttribute特性將類型定義成錯誤契約,相應的形式如下麵的代碼所示。在一般的情況下,如果你定義的服務是為他人所用,比如第三方服務消費者,該錯誤契約的定義是必須的,因為相應的錯誤明細類型需要通過元數據的形式發布出來,指導客戶端如何對接收到的消息進行反序列化。但是,如果服務僅供你自己的應用所用,那麼你可以在運行時動態地添加相應的錯誤描述,從而避免在服務契約的每一個服務操作方法上應用這麼一個FaultContractAttribute

   1: [ServiceContract(Namespace = "https://www.artech.com/")]
   2: public interface ICalculator
   3: {
   4:     [OperationContract]
   5:     [ExceptionHandlingBehavior("myExceptionPolicy")]
   6:     [FaultContract(typeof(ServiceExceptionDetail), Action = "https://www.artech.com/fault")]
   7:     int Divide(int x, int y);
   8: }

我們應用在操作方法上麵的FaultContractAttribute特性,最終會轉換成操作描述(OperationDescription)的錯誤描述(FaultDescription),如果我們在運行時能夠為所有的操作描述添加相應的錯誤描述,就能避免在每個服務操作上麵應用相同的FaultContractAttribute特性。不過,為了服務的重用,我不介意這樣偷懶,所以這種方案僅僅作為研究、學習之用。

一、通過自定義ServiceHost的方式動態添加錯誤描述(服務端)

首先需要在服務端為每一個服務操作添加基於ServiceExceptionDetail的錯誤描述,這可以通過自定ServiceHost來實現。由於服務描述需要在ServiceHost開啟之前生成方才有效(具體的原因,相對比較複雜,大家可以在《WCF技術剖析(卷1)》第7章關於服務寄宿的部分找到答案),所以我們將相關的邏輯定義在OnOpening方法之中。在下麵的代碼中,我定義了這樣一個簡單的ServiceHost:ExceptionHandlingServiceHost。

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Activation;
   4: using System.ServiceModel.Description;
   5:  
   6: namespace Artech.EnterLibIntegration.WcfExtensions
   7: {
   8:     public class ExceptionHandlingServiceHost : ServiceHost
   9:     {
  10:         public ExceptionHandlingServiceHost(Type t, params Uri[] baseAddresses)
  11:             : base(t, baseAddresses)
  12:         { }
  13:  
  14:         protected override void OnOpening()
  15:         {
  16:             base.OnOpening();
  17:             foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
  18:             {
  19:                 foreach (OperationDescription operation in endpoint.Contract.Operations)
  20:                 { 
  21:                     FaultDescription faultDescription = new FaultDescription(ServiceExceptionDetail.FaultAction);
  22:                     faultDescription.DetailType = typeof(ServiceExceptionDetail);
  23:                     operation.Faults.Add(faultDescription);
  24:                 }
  25:             }
  26:         }
  27:     }
  28: }

邏輯相對比較簡單:遍曆所有終結點(ServiceEndpoint),為每一個終結點的契約(ContractDescription)的每一個操作(OperationDescription)添加錯誤明細類型為ServiceExceptionDetail的錯誤描述(FaultDescription),並指定預定義的Action。

對於自定義的ServiceHost,可以直接用於不需要.svc文件進行訪問的寄宿場景,也就是說對於除了IIS和WAS的服務寄宿,可以直接采用自定義的ServiceHost。服務需要在基於IIS和WAS的寄宿方式中采用自定義的ServiceHost,還需要為之創建相應的ServiceHostFactory(關於ServiceHostFactory作用和用法,同樣可以參閱《WCF技術剖析(卷1)》第7章)。下麵,我們為ExceptionHandlingServiceHost定義了一個簡單的ServiceHostFactory:ExceptionHandlingServiceHostFactory。

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Activation;
   4: using System.ServiceModel.Description;
   5:  
   6: namespace Artech.EnterLibIntegration.WcfExtensions
   7: {
   8:     public class ExceptionHandlingServiceHostFactory : ServiceHostFactory
   9:     {
  10:         protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  11:         {
  12:             return new ExceptionHandlingServiceHost(serviceType, baseAddresses);
  13:         }
  14:     }
  15: }

二、通過自定義ChannelFactory<TChanel>的方式動態添加錯誤描述(客戶端)

服務端需要為每一個操作添加基於ServiceExceptionDetail的錯誤描述,以便實現對該對象的序列化;同理,客戶端同樣需要這樣一個錯誤描述,以實現對該對象的反序列化。我們可以將這樣的功能通過一個自定義ChannelFactory<TChannel>來實現。下麵定義的ExceptionHandlingChannelFactory就是這樣一個自定的ChannelFactory<TChannel>。對錯誤描述的添加實現在重寫的CreateDescription方法中:

   1: using System.ServiceModel;
   2: using System.ServiceModel.Description;
   3:  
   4: namespace Artech.EnterLibIntegration.WcfExtensions
   5: {
   6:     public class ExceptionHandlingChannelFactory<TChannel>:ChannelFactory<TChannel>
   7:     {
   8:         public ExceptionHandlingChannelFactory(string endpointConfigurationName)
   9:             : base(endpointConfigurationName)
  10:         { }
  11:  
  12:         protected override ServiceEndpoint CreateDescription()
  13:         {  
  14:             ServiceEndpoint serviceEndpoint = base.CreateDescription();
  15:             foreach (OperationDescription operation in serviceEndpoint.Contract.Operations)
  16:             {
  17:                 FaultDescription faultDescription = new FaultDescription(ServiceExceptionDetail.FaultAction);
  18:                 faultDescription.DetailType = typeof(ServiceExceptionDetail);
  19:                 operation.Faults.Add(faultDescription);
  20:             }
  21:  
  22:             return serviceEndpoint;
  23:         }
  24:     }
  25: }

那麼,對其我們給出的例子,我們就要使用我們上麵創建的這兩個組件了。首先,有了這兩個組件的幫助,在服務契約中,我們再也不需要在繁瑣地為每一個服務操作定義相同的FaultContractAttribute特性了。於是我們先將其拿掉。

   1: [ServiceContract(Namespace = "https://www.artech.com/")]
   2: public interface ICalculator
   3: {
   4:     [OperationContract]
   5:     [ExceptionHandlingBehavior("myExceptionPolicy")]
   6:     int Divide(int x, int y);
   7: }   

然後,再進行服務寄宿的時候,直接利用我們定義的ExceptionHandlingServiceHost就可以了。

   1: using System;
   2: using Artech.EnterLibIntegration.WcfExtensions;
   3: using Artech.WcfServices.Services;
   4: namespace Artech.WcfServices.Hosting
   5: {
   6:     public class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ExceptionHandlingServiceHost host = new ExceptionHandlingServiceHost(typeof(CalculatorService)))
  11:             {
  12:  
  13:                 host.Open();
  14:                 Console.Read();
  15:             }
  16:         }
  17:     }
  18: }

而客戶端,我們可以借助於我們定義的ExceptionHandlingChannelFactory<TChannel>實現對服務代理的創建。

   1: using System;
   2: using Artech.EnterLibIntegration.WcfExtensions;
   3: using Artech.WcfServices.Contracts;
   4: namespace Artech.WcfServices.Clients
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ExceptionHandlingChannelFactory<ICalculator> channelFactory = new ExceptionHandlingChannelFactory<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 (CalculationException ex)
  21:                     {
  22:                         Console.WriteLine(ex.Message);
  23:                         Console.WriteLine("InnerException");
  24:                         Console.WriteLine("\tType:{0}", ex.InnerException.GetType());
  25:                         Console.WriteLine("\tMessage:{0}", ex.InnerException.Message);
  26:                     }
  27:                 }
  28:             }
  29:  
  30:             Console.Read();
  31:         }
  32:     }
  33: }

這樣我們同樣可以得到與上篇一樣的執行結果:

計算錯誤
InnerException
        Type:System.DivideByZeroException
        Message:試圖除以零.

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 11:04:54

  上一篇:go  Enterprise Library深入解析與靈活應用(8):WCF與Exception Handling AppBlock集成[上]
  下一篇:go  《Enterprise Library深入解析與靈活應用》博文係列匯總