概述
通過前面兩篇的介紹,對Web服務定址規範以及在WCF開發中端點位址有了深入的認識。本文我們繼續深入WCF定址第三部分內容,當訊息傳入時,如何來確定匹配的終結點,就是我們本文要講到的訊息篩選引擎。在WCF中,訊息篩選器引擎包括兩個重要的組成部分:篩選器和篩選器表。
認識訊息篩選器
在WCF中當有訊息傳入時,它使用訊息篩選器來確定匹配的終結點,每個終結點實際上關聯著兩個篩選器:一個地址篩選器和一個契約篩選器。地址篩選器確定傳入訊息是否匹配終結點的“To”地址和任何必需的地址前序,而契約篩選器則確定它是否匹配終結點的契約,兩個篩選器都被發送器用來確定目標終結點。
在WCF中,所有的訊息篩選器都繼承於MessageFilter抽象基類,系統內建了幾種的訊息篩選器,1所示:
圖1
EndpointAddressMessageFilter:作為預設的地址篩選器,它會將SOAP訊息中的“To”地址與端點位址進行比較,預期它們完全符合,也會傳入訊息中獲得的定址前序和終結點要求的一組定址前序進行比較,要使最終匹配的結果返回true,必須滿足以下兩個條件:
1. 篩選器的地址統一資源識別項 (URI) 必須與訊息 To 標題中的統一資源識別項相同。
2. 篩選器地址中的每個終結點參數都必須在訊息中找到一個與之匹配的標題。
ActionMessageFilter:作為預設的契約篩選器,它根據傳入的SAOP訊息中“Action”值和契約上的操作進行比較,確定是否匹配匹配。該篩選器在初始化時將包含一個操作字串列表,如果篩選器的列表中的任一操作與訊息或訊息緩衝區中的 Action 標題匹配,則 Match 方法返回 true。 如果該列表為空白,則將該篩選器視為全匹配型篩選器,任何訊息或訊息緩衝區都與該篩選器匹配,並且 Match 返回 true。 如果篩選器列表中沒有任何操作與訊息或訊息緩衝區中的 Action 標題匹配,則 Match 返回 false。 如果訊息中不存在任何操作且篩選器的列表非空,則 Match 返回 false。
PrefixEndpointAddressMessageFilter:此篩選器與 EndpointAddressMessageFilter 執行相同的查詢,不同的是測試訊息是否與端點位址相匹配是由“最長首碼匹配”完成的。這表示篩選器中指定的 URI 不需要與訊息的 URI 完全符合,不過必須作為首碼包含在該 URI 中。例如,如果篩選器指定地址“http://www.foo.com”,並且訊息是發送給“http://www.foo.com/customerA”,則將滿足篩選器查詢條件的 URI 部分,但是篩選器查詢的前序部分仍需要完成。
MatchAllMessageFilter:該篩選器將導致所有訊息都匹配給定終結點,看一下它的Match方法實現,除非訊息為空白,否則就返回True:
[DataContract]public class MatchAllMessageFilter : MessageFilter{ public override bool Match(Message message) { if (message == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message"); } return true; }}
MatchNoneMessageFilter:該篩選器將導致所有訊息都不匹配,看一下它的Match方法實現,除非訊息為空白,否則就返回False:
[DataContract]public class MatchNoneMessageFilter : MessageFilter{ public override bool Match(Message message) { if (message == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message"); } return false; }}
XpathMessageFilter:使用 XPath 1.0 運算式來指定匹配的條件。
除此之外,我們可以自訂自己的訊息篩選器,在本系列的後面將會講到。
篩選器工作原理
正如在前面所看到的,MessageFilter提供了所有篩選器的基類,篩選器中的Match方法用於確定訊息是否滿足篩選器的條件。如下面的程式碼片段中,我們定義兩個ActionMessageFilter和EndpointAddressMessageFilter,然後建立一個Message,看看它們最終匹配的結果:
static void Main(string[] args){ // 建立兩個ActionMessageFilter執行個體 ActionMessageFilter actionFilter1 = new ActionMessageFilter("Add", "Sub"); ActionMessageFilter actionFilter2 = new ActionMessageFilter("Mul"); // 建立兩個EndpointAddressMessageFilter執行個體 EndpointAddressMessageFilter addressFilter1 = new EndpointAddressMessageFilter ( new EndpointAddress("http://localhost:8887/Calculator") ); EndpointAddressMessageFilter addressFilter2 = new EndpointAddressMessageFilter ( new EndpointAddress("http://www.cnblogs.com/terrylee") ); // 建立一個Message,設定Action和To Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "myBody"); message.Headers.Action = "Add"; message.Headers.To = new Uri("http://localhost:8887/Calculator"); // 測試匹配結果 bool actionResult1 = actionFilter1.Match(message); bool actionResult2 = actionFilter2.Match(message); bool addressResult1 = addressFilter1.Match(message); bool addressResult2 = addressFilter2.Match(message); // 輸出結果 Console.WriteLine("The result of filter:"); Console.WriteLine(actionResult1); Console.WriteLine(actionResult2); Console.WriteLine(addressResult1); Console.WriteLine(addressResult2); Console.ReadLine();}
輸出結果2所示:
圖2
在該樣本中,由於我們建立的Message對象,最終的SOAP訊息包如下代碼所示:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> <s:Header> <a:To s:mustUnderstand="1">http://localhost:8887/Calculator</a:To> <a:Action s:mustUnderstand="1">Add</a:Action> </s:Header> <s:Body> </s:Body></s:Envelope>
所以這裡匹配上的是actionFilter1和addressFilter1。注意,一旦構造篩選器,篩選器使用的條件無法更改,因為篩選器表無法檢測更改。修改篩選器的條件的唯一方法是構造一個新的篩選器,然後刪除現有篩選器。
訊息篩選器表
訊息篩選器表用於儲存鍵-值對,其中篩選器為鍵,而與篩選器關聯的資料為值。篩選器資料可用於指示當訊息與篩選器匹配時要採取的操作,篩選器資料的類型是篩選器表類的泛型參數。 篩選器資料可以包含路由規則、會話安全狀態、通道上的接聽程式等等。所有的訊息篩選器都儲存在實現了IMessageFilterTable<TFilterData>的表中,如MessageFilterTable<TFilterData>,在它的內部,又有很多個與具體篩選器類型相關的篩選器表,添加篩選器時,會將其放置在包含此類篩選器的內部篩選器表(如果已存在)中。如果不存在此篩選器表,則調用 CreateFilterTable 以分配一個適當類型的新篩選器表,3所示:
圖3
同時在添加篩選器時,篩選器表會給每個篩選器設定一個預設的優先順序。
設定服務的訊息篩選器
我們可以通過ServiceBehavior來指定服務所用的訊息篩選器,在ServiceBehavior中有一個AddressFilterMode的屬性,它用來指定發送器用於將傳入的訊息路由到匹配的終結點,由三個選項:
Exact:指示對傳入訊息的地址執行精確匹配的篩選器,即使用EndpointAddressMessageFilter。
Prefix:指示對傳入訊息的地址執行最長首碼匹配的篩選器,即使用PrefixEndpointAddressMessageFilter。
Any:指示與傳入訊息的任意地址相匹配的篩選器,即使用MatchAllMessageFilter。
如下面的代碼,指示使用PrefixEndpointAddressMessageFilter作為地址篩選器:
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Prefix)]public class CalculatorService : ICalculator{ public int Add(int x, int y) { return x + y; }}
我們可以在Host端輸出一下EndpointDispatcher所用的地址篩選器和契約篩選器,如下代碼所示:
static void Main(string[] args){ using (ServiceHost calculatorServiceHost = new ServiceHost(typeof(CalculatorService))) { calculatorServiceHost.Opened += delegate { Console.WriteLine("Service begin to listen via the Address:{0}", calculatorServiceHost.BaseAddresses[0].ToString()); }; calculatorServiceHost.Open(); foreach (ChannelDispatcher channelDispatcher in calculatorServiceHost.ChannelDispatchers) { Console.WriteLine(channelDispatcher.BindingName); foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { Console.WriteLine(endpointDispatcher.AddressFilter.ToString()); Console.WriteLine(endpointDispatcher.ContractFilter.ToString()); } Console.WriteLine("---------------------------"); } Console.Read(); }}
輸出結果4所示:
圖4
結束語
本文詳細介紹了WCF中的訊息篩選器,這在某些情境下非常有用,可以根據SOAP訊息標題中包含的資料將訊息發送到不同的地方處理,這在一定程度上可以擺脫WCF的調度模型。WCF定址相關文章:
WCF專題系列(5):深入WCF定址Part 5—邏輯地址和物理地址
WCF專題系列(4):深入WCF定址Part 4—自訂訊息篩選器
WCF專題系列(2):深入WCF定址Part 2—自訂定址前序
WCF專題系列(1):深入WCF定址Part 1—Web服務定址規範