Enterprise Library深入解析與靈活應用(8):WCF與Exception Handling AppBlock集成[下]
在上篇中,我詳細介紹了如何通過自定義ClientMessageInspector和 ErrorHandler,實現WCF與微軟企業庫中的Exception Handling Application Block(EHAB)之間的集成。這個方案的基本思路就是:當異常從服務端拋出,利 用EHAB針對某個配置好的異常處理策略進行處理;然後將處理有的異常通過 ServiceExceptionDetail對象進行封裝,最後序列化置於Fault消息,最終被返回 給客戶端;客戶端接收到該Fault消息後,提取並創建ServiceExceptionDetail對 象,並通過反射重建異常;最後將異常拋出,使客戶端可以根據客戶端配置的異 常處理策略對該異常進行進一步的處理。
為了實現WCF對 ServiceExceptionDetail對象的序列化和反序列化,我們必須通過 FaultContractAttribute特性將類型定義成錯誤契約,相應的形式如下面的代碼 所示。在一般的情況下,如果你定義的服務是為他人所用,比如第三方服務消費 者,該錯誤契約的定義是必須的,因為相應的錯誤明細類型需要通過元數據的形 式發布出來,指導客戶端如何對接收到的消息進行反序列化。但是,如果服務僅 供你自己的應用所用,那麼你可以在運行時動態地添加相應的錯誤描述,從而避 免在服務契約的每一個服務操作方法上應用這麼一個FaultContractAttribute。
1: [ServiceContract(Namespace = "http://www.artech.com/")]
2: public interface ICalculator
3: {
4: [OperationContract]
5: [ExceptionHandlingBehavior ("myExceptionPolicy")]
6: [FaultContract(typeof (ServiceExceptionDetail), Action = "http://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<TChan nel>
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 = "http://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:試圖除以零.
本文配套源碼