在本系列的每篇文章中,我多次提到WCF是一個極具可擴展性的分布是消息通信框架。為了讓讀者對WCF Extension有一個總體的的認識,在這裡我會簡單列舉了我們經常使用的絕大部分的擴展點,以及通過這些擴展點能夠解決實現項目開發中的那些問題。
有一點需要特別提醒的是:對WCF extensions的靈活應用依賴於你對channel layer和service mode dispatching system的深入理解。所以,如果你對channel layer不甚了解,可以參閱本系列的第一個部分(WCF是如何通過Binding進行通信的)和第二部分(如何對Channel Layer進行擴展——創建自定義Channel), 若是想了解更多關於dispatching system的細節,可以參考本系列的第三部分(WCF Service Mode Layer 的中樞—Dispatcher)。
現在,我們按照在上一篇文章的Dispatching的執行流程,來介紹dispatching system中可以用於對WCF進行擴展的對象,已經這個可擴展對象具體解決的問題和擴展的方式。為了利於讀者理解這些可擴展對象具體被使用在Dispatching整個的生命周期的哪個階段,我們在標注Step 1、Step 2…字樣,讀者可以在上一篇文章中查閱對應的步驟在執行怎樣的功能。
1 、自定義InstanceContextProvider(Step 5)
在WCF infrastructure中, InstanceContext是以一個很重要的概念。InstanceContext是什麼呢?簡言之,InstanceContext就是對service instance的封裝(service instance wrapper),對於每一個service instance來講,WCF都會通過一些contextual information對其進行包裝。這些contextual information存在的目的在於讓不同的request關聯到對應的service instance上。對於不同的Instancing Mode(PerCall、PerSession和Singleton),我們往往具有不同的InstanceContext。而對於PerSession方式的instancing mode,InstanceContext顯得尤為重要,原因很簡單,我們必須保證來自同一個Session的request被分發到同一個service instance,不然很難維護其session的信息。
InstanceContext的獲取通過InstanceContextProvider來實現。在WCF中所有的InstanceContextProvider實現了System.ServiceModel.Dispatcher.IInstanceContextProvider interface。
public interface IInstanceContextProvider
{
// Methods
InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel);
void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel);
bool IsIdle(InstanceContext instanceContext);
void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext);
}
GetExistingInstanceContext:當message有ChannelDispather交付到EndpointDispatcher的時候,該方法會被調用去試圖獲取一個已經存在的InstanceContext。比如:PerSession模式下,如果session已經開始,那個會返回綁定到當前session的InstanceContext,否則return null;對於Singleton模式,由於使用一個service instance來處理所有的request,所以一旦service instance被創建,後續的request都將返回同一個InstanceContext,否則return null;而對於PerCall來說,由於對於每個request來說,都需要創建一個新的service instance來處理,所以它永遠是return null。
InitializeInstanceContext:如何GetExistingInstanceContext返回null,將通過這個方法來創建和初始化一個新的InstanceContext.
IsIdle:當所有的InstanceContext操作完成以後,該方法會被調用,返回的bool類型的結果將用作是否對InstanceContext進行清理和回收的依據。如何你不希望對創建的InstanceContext進行回收,那麼你可以將此方法返回為false。比如:Singleton和PerSession模式下就直接return false;而PerCall則return true。
NotifyIdle:當對InstanceContext進行真正的清理和回收時,此方法會被回調。
和3種instancing mode相匹配, WCF定義了3種InstanceContextProvider:
System.ServiceModel.Dispatcher.PerCallInstanceContextProvider
System.ServiceModel.Dispatcher.PerSessionInstanceContextProvider
System.ServiceModel.Dispatcher.SingletonInstanceContextProvider
通過對InstanceContextProvider進行擴展,創建你自定義的InstanceContextProvider,你可以以你需要的方式關聯request和service instance。比如:為了性能的提升,你可能試圖通過一種對象池的機制實現對service instance的創建和提取,當需要使用到某個service instance的時候,先從對象池中獲取該對象,如果不存在再從新創建對象。當service instance調用完畢,將其放入對象池中。這樣避免了過於頻繁的對象創建而引起對性能的影響。有興趣的朋友不妨試著做一做。
注:當你自定義InstanceContextProvider的時候,一般繼承base class:System.ServiceModel.Dispatcher.InstanceContextProviderBase而不是完全實現System.ServiceModel.Dispatcher.IInstanceContextProvider interface。
2 、自定義MessageFilter(Step 6)
通過對dispatching system的介紹,我們了解到:當ChannelDispather通過ChannelListener創建的Channel接收到request message之後,自己不會對message進行處理,而是遍歷自己的EndpointDispatcher集合屬性,找到與request message相匹配的EndpointDispatcher。到底怎樣的匹配規則會被采用呢?
具體實現是這樣的:每個EndpointDispatcher都定義了兩個特殊的屬性:AddressFilter和ContractFilter,它們的類型繼承了abstract class:System.ServiceModel.Dispatcher.MessageFilter。
public abstract class MessageFilter
{
// Methods
protected MessageFilter();
protected internal virtual IMessageFilterTable<FilterData> CreateFilterTable<FilterData>();
public abstract bool Match(Message message);
public abstract bool Match(MessageBuffer buffer);
}
MessageFilter定義了兩個Match重載,所以子類實現該重載實現自定義的匹配規則。在具體實現的時候,會解析request message或者MessageBuffer (message 的memory buffer表示)(一般是message header),來判斷該request是否和對應的EndpointDispatcher相互匹配。
WCF為我們提供了一下6類Message Filter:
System.ServiceModel.Dispatcher.ActionMessageFilter:通過message Action header進行匹配。
System.ServiceModel.Dispatcher.MatchAllMessageFilter:匹配所有的message,也就是直接返回true。
System.ServiceModel.Dispatcher.EndpointAddressMessageFilter:根據message的To header 進行匹配。
System.ServiceModel.Dispatcher.MatchNoneMessageFilter:不會和任何的message匹配,也就是直接返回false。
System.ServiceModel.Dispatcher.PrefixEndpointAddressMessageFilter:和EndpointAddressMessageFilter相似,不過這次匹配的不是整個message的To header ,而是To header 的前綴。
System.ServiceModel.Dispatcher.XPathMessageFilter:給予Xpath expression的匹配方式。
那麼EndpointDispatcher的ContractFilter和AddressFilter采用的又是那種類型的MessageFilter呢?通過Reflector看看EndpointDispatcher的構造函數就會知道,AddressFilter采用的是EndpointAddressMessageFilter,而ContractFilter采用的則是MatchAllMessageFilter。也就是說,當ChannelDispather在為接收到的request message選擇合適的EndpointDispatcher的時候,會根據message To header上的address進行匹配。
你也許會問,如何有不止一個EndpointDispatcher滿足匹配條件怎麼辦呢?答案是:ChannelDispatcher會選擇一個Filter優先級最高的一個,該優先級由EndpointDispather的FilterPriority屬性來決定。
如何你想改變這種默認的filter方式,你可以通過你自定義的behavior,來改變EndpointDispatcher的這兩個Filter:AddressFilter和ContractFilter。你或許會懷疑是否有這樣做的必要,那麼我給你一個具體的例子:我們都知道我們有兩種傳統的Web request方式:Get和Post,前者根據query string,後者基於form。在實際的項目中,你可能會使用到形如Get的方式來訪問WCF service,這在Ajax的應用中應該有這樣的需求,那麼你就不能以現有的Filter方式找到所需的EndpointDispather,這時候,你需要一個特殊的AddressFilter,一個基於解析query string的filter.
3 、自定義MessageInspector(Step 9)
在client端和service端都有自己的MessageInspector,client端叫做ClientMessageInspector,實現System.ServiceModel.Dispatcher.IClientMessageInspector interface,而service端叫做 DispatchMessageInspector, 實現了System.ServiceModel.Dispatcher.IDispatchMessageInspector interface。DispatchMessageInspector允許你在request message交付到具體的DispatchOperation付諸執行之前或者reply message返回client之前對incoming message/outgoing message進行檢驗、修改或者其他一些基於message的操作;
IDispatchMessageInspector的定義很簡單:
public interface IDispatchMessageInspector
{
// Methods
object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);
void BeforeSendReply(ref Message reply, object correlationState);
}
在實際的項目開發中,MessageInspector使用相當廣范,比如:我們可以定義自己的MessageInspector實現Logging功能;或者在Client端通過ClientMessageInspector添加一些與業務無關的context信息,並在service通過DispatchMessageInspector將其取出。在本系列後續的部分我將給你一個application context propagation的具體應用。
4、自定義InstanceProvider(Step 10 & Step 11)
顧名思義,InstanceProvider就是用於創建或者提供service instance的。不過,除了提供service instance的創建者或者提供者的身份外,InstanceProvider還用於service instance的釋放和回收。所有的IntanceProvider實現了System.ServiceModel.Dispatcher.IInstanceProvider interface:
public interface IInstanceProvider
{
// Methods
object GetInstance(InstanceContext instanceContext);
object GetInstance(InstanceContext instanceContext, Message message);
void ReleaseInstance(InstanceContext instanceContext, object instance);
}
如果InstanceProvider對應的DispatchOperation.ReleaseInstanceBeforeCall 為true的話,IntanceProvider將通過DispatchRuntime的InstanceProvide屬性提取出來,通過調用ReleaseInstance()方法釋放掉現有的service instance。
然後才會調用GetInstance方法,得到新的service instance. 基於自定義InstanceProvide的WCF extension也是比較常見的。比較有意義的一個應用是通過自定義InstanceProvider實現AOP。你可以將service instance創建過程中加入一些額外的特性實現method interception。比如: 通過自定義InstanceProvider,你將和容易實現Enterprise library PIAB(Policy Injection Application Block)/Unity Application Block和WCF的集成。我將在本系列後續的文章中介紹相關的實現。
5 、自定義CallContextInitializer (Step 12 & Step 18)
提到CallContextInitializer,我想有一部分人會馬上想到System.Runtime.Remoting.Messaging.CallContext。CallContext為我們創建基於當前線程的Ambient context提供了便利。通過CallConext,我們和容易地將一些contextual information保存在TLS(Thread Local Storage)中。
類似地,DispatchOperation的CallContextInitializers提供了一個CallContextInitializer的集合,這些CallContextInitializer可以幫助我們對TLS進行初始化和釋放回收的工作。比如在某個service 方法被真正之前,我們希望設置一些Context的數據,這些數據可能使業務有關,但大部分是和具體的業務邏輯沒有關系的,比如一些Auditing的數據。在方式執行完成後,對這些context數據進行清理和回收。 WCF下的所有CallContextInitializer實現了System.ServiceModel.Dispatcher.ICallContextInitializer interface:
public interface ICallContextInitializer
{
// Methods
void AfterInvoke(object correlationState);
object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message);
}
再給大家介紹一個使用CallContextInitializer的場景。假設有一個service專門提供message,考慮到Localization,當客戶訪問你的service獲取某項message entry的時候,你希望根據該client的當前culture/UI culture返回具體的message。但是你不希望將這個culture作為API的一部分。這樣你就可以這樣做:在client端,通過自定義ClientMessageInspector將當前的Culture附加到outgoing message的header中。在service端,通過一個自定義的CallContextInitializer,將culture的值從incoming message header取出,並用這個culture設置當前線程的Culture和UICulture。那麼message的獲取只需要考慮當前線程的Culture就可以了。我將在本系列後續的文章介紹這個應用。
6 、自定義MessageFormatter(Step 13 & Step 17)
對整個WCF infrastructure,我們可以將其分成兩個世界,其中一個是基於message的世界;而另一個則是object的世界。對於前者來講,所有的數據通過message進行封裝,後者則同一個個具體的object來呈現。要實現具體的service功能,毫無疑問,需要調用具體的方法,傳入具體的參數,而這些輸入參數是一個個的對象,方法執行完成生成的結果也是一個個的對象。但是我們最初接受的request確實一個message,方法執行的參數也一XML InfoSet的形式封裝在message中;我們最終生成的結果也不能直接以object的形式返回來client。所以我們需要一個這樣的中介:將輸入參數從message中提出,並轉化成object;同是將返回值從object形式轉化成message。這樣的中介就是:MessageFormatter。
和MessageInspector一樣,client端和service的Formatter是不同的。client端叫做ClientMessageFormatter ,實現了System.ServiceModel.Dispatcher.IClientMessageFormatter interface;service端叫做DispatchMessageFormatter, 實現了System.ServiceModel.Dispatcher.IDispatchMessageInspector interface:
public interface IDispatchMessageFormatter
{
// Methods
void DeserializeRequest(Message message, object[] parameters);
Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result);
}
public interface IClientMessageFormatter
{
// Methods
object DeserializeReply(Message message, object[] parameters);
Message SerializeRequest(MessageVersion messageVersion, object[] parameters);
}
運行時真正使用到的MessageFormatter通過ClientOperation和DispatchOperation的Formatter屬性指定。WCF提供的MessageFormatter都是基於DataContract serializer或者XML serializer的。如何現有的這些不能滿足你的需求,你完全可以創建自定義的MessageFormatter,然後通過behavior將其賦值到具體的ClientOperation和DispatchOperation上。
注:並非所有的情況下都需要MessageFormamter來幫助我們從事format的工作。我們知道我們的API可以是基於Message對象的,也就是說我們的輸入可以使一個message,返回值也可以是一個具體的message。這種情況下,我們是不需要MessageFormatter的。WCF實際上是通過ClientOperation或者DispatchOperation的SerializeRequest/DeserializeReply和DeserializeRequest/SerializeReply屬性來判斷是否需要對參數或者返回值進行format的。
7、自定義ParameterInspector(Step14 & Step 16)
Security有這樣的一個原則:不能完全信任來自用戶或者訪問者的輸入。就像Asp.NET通過一個個validator control來保證用戶輸入的合法性一樣,WCF也需要有這樣的機制。而這樣的功能是通過ClientOperation或者DispatchOperation的ParameterInspectors集合實現的。
當執行具體的service method之前,會遍歷DispatchOperation ParameterInspectors集合中的每個ParameterInspector,並調用BeforeCall對輸入參數進行驗證;而當service method被真正執行後,會生成返回值或者輸出參數,在這個時候對ParameterInspectors的遍歷再次進行,不果這次調用的是AfterCall方法,AfterCall方法旨在對返回值或者輸出參數進行驗證。
所有的ParameterInspector均實現了System.ServiceModel.Dispatcher.IParameterInspector interface。
public interface IParameterInspector
{
// Methods
void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState);
object BeforeCall(string operationName, object[] inputs);
}
注: 大家可能已經注意到了,BeforeCall有一個返回值。這個返回值得目的在於同AfterCall進行批評。在調用AfterCall是,這個返回值將會傳入第三個參數:correlationState。
通過自定義ParameterInspector對WCF進行擴展的最典型的應用莫過於Enterprise Library Validation Application Block和WCF的集成,我將在本系列後續的文章對此作介紹。
8 、自定義ErrorHandler(Step19)
無論是對於具體的項目開發也好,還是對Framework的開發也罷,對異常、錯誤的處理都是必須的。通過ErrorHandler對象,你可以很容易地實現對異常的處理。ChannelDispatcher中將一個ErrorHandler的集合定義在ErrorHandlers屬性中。當出現exception的時候,會遍歷這個ErrorHandlers集合中的每個ErrorHandler。調用HandleError方法和ProvideFault方法。
所有的ErrorHandler都實現了System.ServiceModel.Dispatcher.IErrorHandler interface:
public interface IErrorHandler
{
// Methods
bool HandleError(Exception error);
void ProvideFault(Exception error, MessageVersion version, ref Message fault);
}
一般地,將一個exception handling相關的操作定義在HandleError方法中,比如:對記錄exception日志;而ProvideFault則使你能夠自由地提供你自定義的error, 比如為了防止敏感數據外洩,exception replace(用一個新的exception替換原來的exception);為了使client端進行統一的exception handling進行exception wrap(用一個新的exception對原來的exception進行包裝,一般地講原來的exception作為新的exception的inner exception)。
通過自定義ErrorHandler實現對WCF的擴展的典型應用莫過於Enterprise Library Exception Handling Application與WCF的集成。