程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF技術剖析之三十:一個很有用的WCF調用編程技巧[下篇]

WCF技術剖析之三十:一個很有用的WCF調用編程技巧[下篇]

編輯:關於.NET

在《上篇》中,我通過使用Delegate的方式解決了服務調用過程中的異常處理以及對服務代理的關閉。對於《WCF技術剖析(卷1)》的讀者,應該會知道在第7章中我通過類似於AOP的方式解決了相似的問題,現在我們來討論這個解決方案。

通過《服務代理不能得到及時關閉會有什麼後果?》的介紹,我們知道了及時關閉服務代理的重要意義,並且給出了正確的編程方式。如果嚴格按照上面的編程方式,就意味著對於每一個服務調用,都要使用相同的代碼進行異常處理和關閉或中斷服務代理對象。按照我個人的觀點,一個應用程序的每一個角落若充斥著相同的代碼片斷,這是一種很不好的設計。設計的目的在於實現代碼的重用(Reuse),絕非代碼的重復(Duplicate)

所以現在我們的目的是將重復使用的代碼進行單獨維護,在使用到的地方進行重用。思路是這樣:通過一個對象實現對客戶端進行服務訪問的方法調用的劫持,在該對象的內部實現真正的方法調用、服務代理關閉或中斷,以及異常處理。這實際上是一種基於AOP的解決方案,在這裡通過自定義真實代理(RealProxy)的方式來實現服務調用的AOP,這也是為何在本章的開始會花如此多的筆墨介紹真實代理和透明代理的一個重要原因。

下圖所示的順序圖(Sequence Diagram)揭示了具體實現的原理:在定義的RealProxy(ServiceRealProxy)中實現了服務調用、異常處理和信道關閉或中斷。客戶端代碼進行服務調用完全是通過自定義真實代理ServiceRealProxy的透明代理進行的,所以所有的方法調用都會直接分發給ServiceRealProxy對象。ServiceRealProxy根據當前方法調用的上下文(比如參數、MethodBase等)構建ChannelFactory<T>對象並創建真正的服務代理對象。然後ServiceRealProxy借助創建出來的服務代理進行真正的服務調用,如果服務調用正常完成,則調用Close方法關閉服務代理,如果在調用過程中拋出CommunicationException和TimeoutException這兩個異常,則調用Abort方法強行中斷服務代理。最後,將服務調用的結果或拋出的異常通過TransparentProxy返回給客戶端代碼。

本例僅僅是為如何通過AOP進行WCF服務調用提供一種思路,並不是一個完備的解決方法(比如,沒有考慮安全認證和客戶端憑證的設置;沒有考慮到雙向通信和回調等),有興趣的讀者可以在此繼承上進一步地完善。現在,就一步步地進行演示。

步驟一:創建ChannalFactory<T>的靜態工廠:ChannelFactoryCreator

由於服務調用通過服務代理完成,而ChannelFactory<T>是服務代理的創建者,所以在這裡先定義一個ChannelFactoryCreator的靜態工廠類,通過它來創建或獲取ChannelFactory<T>方法。由於ChannelFactory<T>的創建是一件費時的工作,為了提供更好的性能,和ClientBase<T>一樣采用了ChannelFactory<T>的緩存機制(《ClientBase<T>中對ChannelFactory<T>的緩存機制》)。不過,這裡的緩存機制比ClientBase<T>的實現要簡單得多,ClientBase<T>通過終結點配置名稱、終結點地址和回調對象三者進行緩存,這裡僅僅是通過終結點配置名稱進行ChannelFactory<T>的緩存,因為我們假設客戶端完全使用配置的終結點進行服務調用(這也是我們推薦的使用方式)。下面是整個ChannelFactory<T>的靜態工廠類的定義:

   1: using System;
2: using System.Collections;
3: using System.ServiceModel;
4: namespace Artech.ServiceProxyFactory
5: {
6: internal static class ChannelFactoryCreator
7: {
8: private static Hashtable channelFactories = new Hashtable();
9:
10: public static ChannelFactory<T> Create<T>(string endpointName)
11: {
12: if (string.IsNullOrEmpty(endpointName))
13: {
14: throw new ArgumentNullException("endpointName");
15: }
16:
17: ChannelFactory<T> channelFactory = null;
18:
19: if(channelFactories.ContainsKey(endpointName))
20: {
21: channelFactory = channelFactories[endpointName] as ChannelFactory<T>;
22: }
23:
24: if (channelFactory == null)
25: {
26: channelFactory = new ChannelFactory<T>(endpointName);
27: lock (channelFactories.SyncRoot)
28: {
29: channelFactories[endpointName] = channelFactory;
30: }
31: }
32:
33: return channelFactory;
34: }
35: }
36: }

ChannelFactoryCreator中通過一個Hashtable類型的靜態變量保存所有創建出來ChannelFactory<T>集合,Hashtable的Key為表示終結點配置名稱的字符串。在Create<T>方法中,先通過傳入的終結點配置名稱查看緩存中是否存在已經創建好的ChannelFactory<T>對象,如果存在則直接返回,否則創建新的ChannelFactory<T>對象,並在返回之前將其加入緩存。

步驟二:創建自定義RealProxy:ServiceRealProxy<T>

ServiceRealProxy<T>實現了真正的服務調用、異常處理和對服務代理的關閉或中斷。ServiceRealProxy<T>的構造函數參數endpointName表示用於服務調用而采用的終結點配置名稱。在Invoke中,先借助於ChannelFactoryCreator獲得的ChannelFactory<T>創建服務代理對象。然後通過解析參數msg(表示對方法的調用)獲得方法調用的參數,並在try控制塊中通過反射,傳入參數調用服務代理對象相應的方法,從而實現了對服務的調用。對於正常服務調用的結果,將其封裝成ReturnMessage對象,並在返回之前調用Close方法關閉服務代理。

在catch控制塊中,對拋出的異常進行處理,由於是通過反射方式實現的方法調用,得到的異常類型基本上都是TargetInvocationException,真正進行服務調用的異常被作為捕獲異常的內部異常(InnerException)。所以,我們會判斷內部異常是否為CommunicationException或TimeoutException,來決定是否通過調用Abort方法強行中斷服務代理。捕獲的異常被封裝成ReturnMessage對象返回。

   1: using System;
2: using System.Runtime.Remoting.Messaging;
3: using System.Runtime.Remoting.Proxies;
4: using System.ServiceModel;
5: namespace Artech.ServiceProxyFactory
6: {
7: public class ServiceRealProxy<T>: RealProxy
8: {
9: private string _endpointName;
10:
11: public ServiceRealProxy(string endpointName):base(typeof(T))
12: {
13: if (string.IsNullOrEmpty(endpointName))
14: {
15: throw new ArgumentNullException("endpointName");
16: }
17: this._endpointName = endpointName;
18: }
19:
20: public override IMessage Invoke(IMessage msg)
21: {
22: T channel = ChannelFactoryCreator.Create<T>(this._endpointName).CreateChannel();
23: IMethodCallMessage methodCall = (IMethodCallMessage)msg;
24: IMethodReturnMessage methodReturn = null;
25: object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
26: methodCall.Args.CopyTo(copiedArgs, 0);
27: try
28: {
29: object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs);
30: methodReturn = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, methodCall.LogicalCallContext, methodCall);
31: (channel as ICommunicationObject).Close();
32: }
33: catch (Exception ex)
34: {
35: if (ex.InnerException is CommunicationException || ex.InnerException is TimeoutException)
36: {
37: (channel as ICommunicationObject).Abort();
38: }
39:
40: if (ex.InnerException != null)
41: {
42: methodReturn = new ReturnMessage(ex.InnerException, methodCall);
43: }
44: else
45: {
46: methodReturn = new ReturnMessage(ex, methodCall);
47: }
48: }
49:
50: return methodReturn;
51: }
52: }
53: }

步驟三:創建自定義服務代理工廠:ServiceProxyFactory

在本案例中,對於最終的客戶端代碼來說,它利用的是上面創建的自定義真實代理的透明代理間接地進行服務調用。而該透明代理就是客戶端的服務代理,為了便於編程,在這裡我們定義一個服務代理的靜態工廠類:ServiceProxyFactory

   1: using System;
2: namespace Artech.ServiceProxyFactory
3: {
4: public static class ServiceProxyFactory
5: {
6: public static T Create<T>(string endpointName)
7: {
8: if (string.IsNullOrEmpty(endpointName))
9: {
10: throw new ArgumentNullException("endpointName");
11: }
12: return (T)(new ServiceRealProxy<T>(endpointName).GetTransparentProxy());
13: }
14: }
15: }

步驟四:通過ServiceProxyFactory創建服務代理進行服務調用

由於重復繁瑣的工作已經在ServiceRealProxy<T>中完成,所以客戶端進行服務調用的代碼將會變得很簡潔。為了驗證在每次調用完畢後,是否如我們所願將信道關閉,我們將ServiceProxyFactory應用到我們熟悉的計算服務的例子(終結點calculatorservice配置的綁定類型為WSHttpBinding)。

   1: ICalculator calculator = ServiceProxyFactory.Create<ICalculator>("calculatorservice");
2: for (int i = 1; i < 2000; i++)
3: {
4: Console.WriteLine("{3}: x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2), i);
5: }

輸出結果:

1 : x + y = 3 when x = 1 and y = 2
2 : x + y = 3 when x = 1 and y = 2
......
1999: x + y = 3 when x = 1 and y = 2
2000: x + y = 3 when x = 1 and y = 2

從輸出的結果可以看出,2000次服務調用成功完成,由此可以證明每次服務調用結束後,會話信道都被成功關閉。會話信道的自動關閉或中斷還帶來一個好處,由於每次使用的是新信道,所以即使上一個服務調用出錯,也不會影響後續的調用。下面的例子證明了這一點:

   1: ICalculator calculator = ServiceProxyFactory.Create<ICalculator>("calculatorservice");
2: try
3: {
4: Console.WriteLine("x / y = {2} when x = {0} and y = {1}",2,0,calculator.Divide(2,0));
5: }
6: catch(Exception ex)
7: {
8: Console.WriteLine(ex.Message);
9: }
10: Console.WriteLine("x + y = {2} when x = {0} and y = {1}",2,0,calculator.Add(2,0));

輸出結果:

The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the <serviceDebug> configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.

x + y = 2 when x = 2 and y = 0
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved