Original (原創) by Teddy’s Knowledge Base
Content (目錄)
(1) WCF Configuration Centralization (WCF配置集中管理)
(2) WCF Automatic Deployment (WCF自動化部署)
(3) WCF Automatic Service Locating (WCF自動化服務定位)
(4) WCF Database Paging & Sorting (WCF數據庫分頁和排序)
(5) WCF Based ASP.NET DataSouce Control (基於WCF的數據源控件)
(6) 1 + 2 + 3 + 4 + 5 = ?
摘要
本文提供一種在WCF服務消費應用程序中通過與服務提供應用程序共享WCF服務契約接口來自動化定位WCF服務實現的方案。
正文
什麼是服務定位?
服務定位的意思是服務的消費者給定服務的接口類型,由一個稱作服務定位器的對象返回這個服務接口的實現類,這樣,服務的消費者對服務的實現類就沒有依賴關係。服務定位器這個設計模式的思路誕生於JAVA世界。這個模式又發展成了IoC容器模式。Martin Folwer對這個設計模式已經討論很多了。
WCF的ChannelFactory類實現了服務定位器設計模式
實際上,WCF的ChannelFactory類也實現了服務定位器模式,它提供了一個接受一個端點配置名稱的構造函數,通過這個名稱,ChannelFactory就能從應用程序配置文件中讀取配置信息,構造綁定信息。構造了ChannelFactory實例以後,就能調用它的CreateChannel()方法來創建服務契約的代理了。一個代理其實是一個動態實現的服務契約的實現類或子類。下面是一個使用ChannelFactory的示例:
1 ChannelFactory<IRequestChannel> factory
2
3 = new ChannelFactory<IRequestChannel>("MyEndpoint");
4
5 IRequestChannel proxy = factory.CreateChannel();
6
7 //using proxy instance…
和ServiceHost類類似,ChannelFactory類也提供接受Binding類實例的構造函數。所以,也可以用編程方式構造一個Binding類的實例來構造一個ChannelFactory。
包裝ChannelFactory類
ChannelFactory類有下面這些限制:
- 作爲一個服務定位器的實現,它僅支持從應用程序配置文件中讀取配置信息。
- 就像我在第一章的“提示”部分介紹的,客戶程序必須總是很小心的關閉ChannelFactory以釋放它佔用的資源。
要克服這些限制,我們可以考慮對將ChannelFactory類包裝成一個安全的WCF服務定位器。這樣一個服務定位器的實現要注意下面幾點:
- 這個WCF服務定位器應該可以從其他地方如集中化的配置存儲讀取用於構造Binding的配置信息,而不是隻能從應用程序配置文件中。
- 這個WCF服務定位創建的ChannelFactory的資源釋放應該以最佳實現包裝在這個WCF服務定位器的IDsiposable接口和析構函數的實現中。客戶程序原則上應該總是要記得在消費完服務以後顯式的地調用WCF服務定位器的Dispose()方法。儘管,即使漏了顯式調用,他的析構函數也應該能在實例被垃圾回收的時候調用Dispose()。
- 這個WCF服務定位器不應該被全局緩存或實現爲單例模式,因爲構造和保持ChannelFactory的資源開銷是很昂貴的,所以應該儘早在消費完服務以後釋放。
下面是一個WCF服務定位器的示例:
1 public sealed class WcfServiceLocator : IServiceLocator
2 {
3 #region Private Members
4
5 //…
6
7 #endregion
8
9 #region Public Methods
10
11 /// <summary>
12 /// Generic version of GetService
13 /// </summary>
14 /// <typeparam name="T">The service contract type</typeparam>
15 /// <returns>The service proxy instance</returns>
16 public T GetService<T>()
17 {
18 if (!_channelFactories.ContainsKey(typeof(T)))
19 {
20 lock (_channelFactories)
21 {
22 if (!_channelFactories.ContainsKey(typeof(T)))
23 {
24 var cf = CreateChannelFactory<T>();
25 if (cf != null)
26 {
27 _channelFactories.Add(typeof(T), cf);
28 try
29 {
30 _serviceProxies.Add(typeof(T), cf.CreateChannel());
31 }
32 catch
33 {
34 _serviceProxies.Add(typeof(T), null);
35 }
36 }
37 else
38 {
39 _channelFactories.Add(typeof(T), null);
40 _serviceProxies.Add(typeof(T), null);
41 }
42 }
43 }
44 }
45
46 return (T)_serviceProxies[typeof(T)];
47 }
48
49 #endregion
50
51 #region IDisposable Members
52
53 /// <summary>
54 /// The dispose method,
55 /// closing all created channel factories in this service locator
56 /// </summary>
57 public void Dispose()
58 {
59 Dispose(true);
60 GC.SuppressFinalize(this);
61 }
62
63 private bool disposed;
64
65 private void Dispose(bool disposing)
66 {
67 if (disposed) return;
68 if (disposing)
69 {
70 foreach (var item in _channelFactories.Values)
71 {
72 if (item != null)
73 {
74 //close channel factory in best practice
75 //refer to: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx
76 try
77 {
78 item.Close();
79 }
80 catch (CommunicationException)
81 {
82 item.Abort();
83 }
84 catch (TimeoutException)
85 {
86 item.Abort();
87 }
88 catch (Exception)
89 {
90 item.Abort();
91 throw;
92 }
93 }
94 }
95 }
96
97 disposed = true;
98 }
99
100 ~WcfServiceLocator()
101 {
102 Dispose(false);
103 }
104
105 #endregion
106 }
整合第三方的IoC容器
如果所有的服務全都用WCF服務實現,那麼上面這個WCF服務定位器就足夠好了。但是,大多數程序同時會使用諸如輔助工具、日誌記錄、第三方組件包裝等等本地服務。這樣一來我們就同樣需要諸如Unity和Castle IoC容器這樣的本地服務定位器。所以,如果我們能夠整合WCF服務定位器和第三方的服務定位器,那就更好了。
下面是一個示例的ServiceManager類,它擁有整合第三方服務定位器和上述WCF服務定位器的能力:
1 public interface IServiceLocator : IDisposable
2 {
3 /// <summary>
4 /// Get service
5 /// </summary>
6 /// <param name="serviceContract">The service contract type</param>
7 /// <returns>The service instance</returns>
8 object GetService(Type serviceContract);
9
10 /// <summary>
11 /// Generic version of GetService
12 /// </summary>
13 /// <typeparam name="T">The service contract type</typeparam>
14 /// <returns>The service instance</returns>
15 T GetService<T>();
16 }
17
18 public sealed class ServiceManager
19 {
20 #region Private Singleton
21
22 private static readonly ServiceManager _singleton = new ServiceManager();
23
24 #endregion
25
26 #region Private Constructor
27
28 private readonly Type _externalServiceLocatorType;
29 private readonly List<Type> _cachedExternalServiceTypes = new List<Type>();
30 private readonly List<Type> _cachedWcfServiceTypes = new List<Type>();
31
32 private ServiceManager()
33 {
34 var serviceLocatorTypeName = ConfigurationManager.AppSettings[Constants.ExternalServiceLocatorTypeAppSettingName];
35 if (!string.IsNullOrEmpty(serviceLocatorTypeName))
36 {
37 var serviceLocatorType = Type.GetType(serviceLocatorTypeName);
38 if (serviceLocatorType != null)
39 {
40 if (serviceLocatorType != typeof(WcfServiceLocator))
41 {
42 _externalServiceLocatorType = serviceLocatorType;
43 }
44 }
45 }
46 }
47
48 #endregion
49
50 #region Public Methods
51
52 /// <summary>
53 /// Get service locator of specified service contract type
54 /// </summary>
55 /// <param name="serviceContract">The service contract type</param>
56 /// <returns>The service instance</returns>
57 public static IServiceLocator GetServiceLocator(Type serviceContract)
58 {
59 if (serviceContract == null)
60 throw new ArgumentNullException("serviceContract");
61
62 if (_singleton._externalServiceLocatorType != null)
63 {
64 if (!_singleton._cachedExternalServiceTypes.Contains(serviceContract))
65 {
66 if (_singleton._cachedWcfServiceTypes.Contains(serviceContract))
67 {
68 return new WcfServiceLocator();
69 }
70 lock (_singleton)
71 {
72 if (!_singleton._cachedExternalServiceTypes.Contains(serviceContract))
73 {
74 if (_singleton._cachedWcfServiceTypes.Contains(serviceContract))
75 {
76 return new WcfServiceLocator();
77 }
78
79 //rethrow the exception in initializing the locator instance directly
80 var locator = (IServiceLocator)Activator.CreateInstance(_singleton._externalServiceLocatorType);
81
82 object service = null;
83 try
84 {
85 service = locator.GetService(serviceContract);
86 if (service != null)
87 {
88 _singleton._cachedExternalServiceTypes.Add(serviceContract);
89 return locator;
90 }
91 }
92 catch
93 {
94 //if could not locate the service
95 _singleton._cachedWcfServiceTypes.Add(serviceContract);
96 return new WcfServiceLocator();
97 }
98 finally
99 {
100 if (service != null)
101 {
102 var disposable = service as IDisposable;
103 if (disposable != null)
104 disposable.Dispose();
105 }
106 }
107 }
108 }
109 }
110 else
111 {
112 var locator = (IServiceLocator)Activator.CreateInstance(_singleton._externalServiceLocatorType);
113 return locator;
114 }
115 }
116
117 _singleton._cachedWcfServiceTypes.Add(serviceContract);
118 return new WcfServiceLocator();
119 }
120
121 /// <summary>
122 /// Get service locator of specified service contract type
123 /// </summary>
124 /// <typeparam name="T">The service contract type</typeparam>
125 /// <returns></returns>
126 public static IServiceLocator GetServiceLocator<T>()
127 {
128 return GetServiceLocator(typeof(T));
129 }
130
131 #endregion
132 }
注意:
ServiceManager類的GetService()的返回類型應該要是IServiceLocator,而不是service本身的實例,因爲對於WCF服務定位器來說,我們應該在消費完服務之後立即顯式調用Dispose()方法來釋放被佔用的資源。 這個ServiceManager類也可以用單例模式實現代替這裏的靜態類方式。 注意ServiceManager類中的高亮代碼,它爲服務契約和服務定位器的映射的緩存實現了一個雙重檢測鎖,確保其線程安全。提示
要使用本文提到的WCF服務定位器或ServiceManager類,應該讓服務的消費程序和提供程序都直接引用共同的定義了服務契約的DLL。不要讓客戶程序使用生成工具通過發佈的元數據生成的代理類。參考
(1) SOA Design Pattern Catalog: http://www.soapatterns.org/
//我是結尾符,待續…