WCF客戶端運行時架構體係詳解[上篇]
客戶端調用WCF服務的方式不外乎有兩種:其一、通過代碼生成工具(比如SvcUtil.exe)導入服務的元數據生成服務代理相關的類型;其二、通過ChannelFactory<TChannel>創建服務代理對象。對於前者,生成的服務代理是一個繼承自ClientBase<TChannel>的類型。對於這樣一個服務代理對象,其內部本質上還是借助於ChannelFactory<TChannel>創建真正用於進行服務調用的代理對象。對於WCF客戶端應用編程接口來說,ChannelFactory<TChannel>是一個核心類型。
目錄
一、創建ChannelFactory<TChannel>
二、客戶端架構體係
信道初始化
消息檢驗
操作和操作選擇
三、 客戶端操作(ClientOperation)
服務調用的本質實際上是針對服務的某個終結點的調用,說得具體地應該是:客戶端通過相匹配的終結點調用服務的終結點。終結點具有ABC三要素,這裏所說的“相匹配”的終結點具體體現在這三要素的匹配上。而服務調用最終體現在消息交換上,接下來我們從消息交換的角度來談談匹配終結點在服務調用的必要性。
- 地址(Address):地址作為調用服務的唯一標識並代表了服務所在的位置,客戶端終結點必須具有一個正確的地址才能確保請求的消息被發送到正確的目的地;
- 綁定(Binding):作為信道層的締造者,綁定最終創建了用於實現消息處理和傳輸的信道信道棧。客戶端必須具有一個與服務端一致的信道棧,才能確保消息的一致性處理。具體來說,客戶端必須具有於服務端一致的傳輸信道,才能確保消息能夠被正常地傳輸到服務端。如果服務端具有采用一個基於HTTP協議的傳輸信道進行請求的監聽,客戶端就不能使用一個基於TCP的傳輸信道。服務端和客戶端必須具有一個相同的消息編碼信道才能確保被一方編碼的消息能夠被另一個解碼。如果服務端采用基於文本的消息編碼信道,客戶端采用的消息編碼信道就不能是基於二進製的。此外,幾乎所有的WS-*規範在WCF的實現都是通過自定義信道來控製消息交換來完成的,所以這也要求客戶端和服務端必須具有對等的信道設置;
- 契約(Contract):契約最終決定了基於某個操作的服務調用應該采用的消息交換模式,以及參與消息交換的消息本身所具有的結構。為了讓客戶端和服務端就此達成一致,必要要求雙方采用等效的契約。
用於創建服務代理對象的ChannelFactory<TChannel>對象本身就是基於某個具體的客戶端終結點創建的。你可以通過編程的方式(構造函數)指定終結點的三要素,也可以將此三要素定義在配置文件中,通過終結點配置名稱(構造函數的endpointConfigurationName參數)來創建ChannelFactory<TChannel>。下麵的代碼片斷給出了相關定義。
1: public abstract class ChannelFactory : CommunicationObject, IChannelFactory, ICommunicationObject, IDisposable
2: {
3: public ServiceEndpoint Endpoint { get; }
4: }
5: public class ChannelFactory<TChannel> : ChannelFactory, IChannelFactory<TChannel>, IChannelFactory, ICommunicationObject
6: {
7: //其他成員
8: public ChannelFactory(string endpointConfigurationName);
9: protected ChannelFactory(Type channelType);
10: public ChannelFactory(Binding binding, EndpointAddress remoteAddress);
11: public ChannelFactory(Binding binding, string remoteAddress);
12: public ChannelFactory(string endpointConfigurationName, EndpointAddress remoteAddress);
13: }
當我們通過調用構造函數創建某個ChannelFactory<TChannel>對象後,WCF會根據指定的終結點創建一個ServiceEndpoint對象。而該ServiceEndpoint就是ChannelFactory<TChannel>對象的核心,隻讀屬性Endpoint返回的也就是這個ServiceEndpoint對象。ServiceEndpoint在ChannelFactory<TChannel>中的結構分布如下圖所示。
WCF服務端架構體係的建立始於ServiceHost的開啟,而整個架構體係根據創建ServiceHost時初始化的用於描述服務的ServiceDescription對象來構建的。與此類似,當我們開啟ChannelFactory<TChannel>的時候,WCF會根據之前創建的ServiceEndpoint來構建客戶端的運行時架構體係。
下圖揭示了WCF客戶端框架體係的大體結構。在該架構體係中,表示客戶端運行時的ClientRuntime是其核心。當ChannelFactory<TChannel>開啟的時候,Binding的BuildChannelFactory<TChannel>方法會被調用,其結果就是:調用所有綁定元素的同名方法,並將創建出來的信道工廠組合成信道工廠棧。而連接它和ClientRuntime的是一個名為ServiceChannelFactoryOverXxx的對象。根據由消息交換模式決定的信道形狀(Channel Shape)和是否支持會話,ServiceChannelFactoryOverXxx具體可分為6種:ServiceChannelFactoryOverOutput/ServiceChannelFactoryOverOutputSession、ServiceChannelFactoryOverRequest/ServiceChannelFactoryOverRequestSession、ServiceChannelFactoryOverDuplex/ServiceChannelFactoryOverDuplex。
ClientRuntime是與DispatchRuntime相匹配的位於客戶端的運行時,也是整個客戶端框架體係的核心,以及我們正對客戶端進行擴展頻繁使用到的對象。接下來,我們也從擴展的角度來介紹客戶端運行時。
信道初始化
ClientRuntime具有兩個基於信道初始化器(ChannleInitializer)列表的屬性,分別是ChannelInitializers和InteractiveChannelInitializers。
1: public sealed class ClientRuntime
2: {
3: //其他成員
4: public SynchronizedCollection<IChannelInitializer> ChannelInitializers { get; }
5: public SynchronizedCollection<IInteractiveChannelInitializer> InteractiveChannelInitializers { get; }
6: }
其中ChannelInitializers中元素被稱為ChannelInitializer,實現了IChannelInitializer接口。如下麵的代碼片斷所示,IChannelInitializer具有唯一的Initialize方法用以對客戶端信道(以IClientChannel對象表示)進行初始化。當客戶端信道被創建之後,客戶端運行時的每個ChannelInitializer的Initialize方法會被調用。
1: public interface IChannelInitializer
2: {
3: void Initialize(IClientChannel channel);
4: }
InteractiveChannelInitializers屬性表示的信道初始化器被稱為InteractiveChannelInitializer,實現了IInteractiveChannelInitializer接口。如下麵的代碼片斷所示,該接口具有一組以異步模式定義的方法:BeginDisplayInitializationUI和EndDisplayInitializationUI。一般情況下,我們通過自定義InteractiveChannelInitializer提供一個指定客戶端用戶憑證的UI。
1: public interface IInteractiveChannelInitializer
2: {
3: IAsyncResult BeginDisplayInitializationUI(IClientChannel channel, AsyncCallback callback, object state);
4: void EndDisplayInitializationUI(IAsyncResult result);
5: }
消息檢驗
對於服務端來說,當請求消息被反序列化之前,回複消息在序列化之後,它們會被分發給DispatchRuntime的DispatchMessageInspector列表以實現針對消息的後續處理。我們將這個機製成為“消息檢驗(Message Inspection)”。消息檢驗機製同樣應用於客戶端。具體來說,ClientRuntime同樣具有一組消息檢驗器,對應於它的隻讀屬性MessageInspectors。
1: public sealed class ClientRuntime
2: {
3: //其他成員
4: public SynchronizedCollection<IClientMessageInspector> MessageInspectors { get; }
5: }
不過它們的名稱為ClientMessageInspector,實現了具有如下定義的IClientMessageInspector接口。當被序列化後的請求消息被分發到信道層之前,接收到的回複消息被反序列化之後,都會被分發給ClientRuntime的ClientMessageInspector列表。在這兩種情況下,BeforeSendRequest和AfterReceiveReply這兩個方法分別被調用,實現針對於請求消息和回複消息的消息檢驗。
1: public interface IClientMessageInspector
2: {
3: void AfterReceiveReply(ref Message reply, object correlationState);
4: object BeforeSendRequest(ref Message request, IClientChannel channel);
5: }
操作和操作選擇
作為服務描述的OperationDescription對象在服務端運行時被轉化成DispatchOperation對象,而客戶端則被轉化成一個ClientOperation對象。ClientRuntime的Operations屬性包含一個ClientOperation的列表,用於表示定義在當前終結點契約的所有操作。
針對某個具體的服務調用,客戶端必須針對當前的調用上下文從該操作列表中選擇一個正確的ClientOperation對象。服務端運行時的操作選擇機製可以實現在一個被稱為DispatchOperationSelector的組件中。客戶端也具有相似的操作選擇機製,而操作選擇器被稱為ClientOperationSelector,實現了一個具有如下定義的IClientOperationSelector接口。具體的操作選擇機製實現在SelectOperation方法中,傳入的參數分別表示代表操作方法的MethodBase對象和傳入的參數列表,而返回值表示最終選擇的操所名稱。具有布爾類型返回值的屬性AreParametersRequiredForSelection則表示實施操作選擇邏輯是否依賴於參數。
1: public interface IClientOperationSelector
2: {
3: string SelectOperation(MethodBase method, object[] parameters);
4: bool AreParametersRequiredForSelection { get; }
5: }
客戶端最終采用的操作選擇器通過屬性OperationSelector表示。除了Operations和OperationSelector屬性之外,ClientRuntime還具有一個額外的屬性UnhandledClientOperation。和DispatchRuntime的UnhandledDispatchOperation屬性類似,此屬性表示的ClientOperation並不存在於Operations屬性表示的操作列表中。當操作選擇器不能正確定找到相應的ClientOperation是,此屬性表示的ClientOperation會被自動用於處理當前的服務調用。一般地,當你在定義服務契約的時候,將OperationContractAttribtue特性的Action定義成“*”時,ClientRuntime的UnhandledClientOperation屬性就代表這個操作。Operations、OperationSelector和UnhandledClientOperation屬性在ClientRuntime中的定義如下麵的代碼片斷所示。
1: public sealed class ClientRuntime
2: {
3: //其他成員
4: public SynchronizedKeyedCollection<string, ClientOperation> Operations { get; }
5: public IClientOperationSelector OperationSelector { get; set; }
6: public ClientOperation UnhandledClientOperation { get; }
7: }
至此,我們對ClientRuntime具有的可擴展組件進行了全麵的介紹,這些組件在ClientRuntime中的分布大致可以通過下圖表示。
代表客戶端運行時的ClientRuntime的核心是一組代表定義在當前終結點契約中的所有操作的ClientOperation列表,我們很有必要對ClientOperation進行深入的了解。下麵的代碼片斷列出了定義在ClientOperation的主要屬性。
1: public sealed class ClientOperation
2: {
3: //其他成員
4:
5: public string Name { get; }
6: public string Action { get; }
7: public string ReplyAction { get; }
8: public bool IsOneWay { get; set; }
9:
10:
11: public MethodInfo SyncMethod { get; set; }
12: public MethodInfo BeginMethod { get; set; }
13: public MethodInfo EndMethod { get; set; }
14:
15: public bool SerializeRequest { get; set; }
16: public bool DeserializeReply { get; set; }
17:
18: public bool IsInitiating { get; set; }
19: public bool IsTerminating { get; set; }
20:
21: public IClientMessageFormatter Formatter { get; set; }
22: public SynchronizedCollection<FaultContractInfo> FaultContractInfos { get; }
23: public SynchronizedCollection<IParameterInspector> ParameterInspectors { get; }
24: }
在定義服務契約的時候,我們通過應用OperationContractAttribute特性將定義在契約接口或類中的某個方法定義成服務操作。當我們針對某個終結點創建ChannelFactory<TChannel>的時候,反映操作描述的OperationDescription被創建出來。而當我們開啟了ChannelFactory<TChannel>之後,OperationDescription對象被轉變成真正的運行時操作對象ClientOperation。所以ClientOperation主要來源於OperationDescription,而最終決定於應用在操作方法上的OperationContractAttribute的定義。
首先,ClientOperaiton的Name、Action、ReplayAction和IsOneway對應於OperationContractAttribute特性的同名屬性。而SyncMethod和BeginMethod/EndMethod則表示同步和異步調用時對應的MethodInfo對象。具體來說,當我們通過將應用在草最方法的OperationContractAttribute特性的AsyncPattern屬性設置成true以定義異步模式的服務操作的情況下,BeginMethod/EndMethod屬性對應於BeginXxx/EndXxx方法。關於具有異步模式的操作定義,請參閱《WCF技術剖析(卷1)》第4章《服務契約(Service Contract)》。
布爾類型的屬性SerializeRequest/DeserializeReply分別表示是否需要對請求消息進行序列化,以及對回複消息進行反序列化。如果操作僅僅具有一個唯一的類型為Message的參數,就無需對參數進行序列化。相應地,如果返回值(或者ref/out參數)也是一個唯一的Message對象,那麼也無需對回複消息進行反序列化。另為一組布爾類型的屬性IsInitiating/ IsTerminating對應於OperationContractAttribute特性的同名屬性,表示在支持會話(Session)的情況下,相應的操作是否是用於初始化/終止會話的操作。
DispatchOperation使用DispatchMessageFormatter進行請求消息的反序列化和回複消息的序列化。與之類似,ClientOperation則采用ClientMessageFormatter進行請求消息的序列化和回複消息的反序列化。ClientMessageFormatter實現了一個具有如下定義的IClientMessageFormatter接口。上述的序列化和反序列化的操作分別實現在SerializeRequest和DeserializeReply方法中。而真正被使用的ClientMessageFormatter定義在ClientOpoeration的Formatter屬性中。
1: public interface IClientMessageFormatter
2: {
3: object DeserializeReply(Message message, object[] parameters);
4: Message SerializeRequest(MessageVersion messageVersion, object[] parameters);
5: }
而最後一個FaultContractInfos屬性表述一個元素為FaultContractInfo的集合。該集合最終用於在出現異常時輔助實現針對錯誤消息(Fault Message)的序列化和反序列化。
和DispatchOperation一樣,ClientOperation具有一個ParameterInspectors屬性表示一組參數檢驗器列表。DispatchOperation和ClientOperation的參數檢驗器實現了相同的接口IParameterInspector。我們可以自定義參數檢器實現針服務調用前對輸入參數的驗證,以及服務調用後對返回值和輸出參數的驗證。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 16:04:37