概述
通過前面兩篇的介紹,對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的調度模型。