託管與使用 WCF 服務通常經歷基本步驟:實作服務、配置端點、託管服務、產生 WSDL 或啟用中繼資料交換讓客戶端產生 Proxy、編寫程式碼實例化 Proxy、呼叫服務操作。雖無需深入內部原理,但即使最簡單情況,客戶端與服務通道也依賴相容配置處理定址語義與訊息篩選,確保呼叫正確操作。
有時在客戶端與目標服務間引入中介服務或路由器服務,以攔截訊息或執行日誌記錄、優先級路由、上線/離線路由、負載平衡、建立安全邊界等活動極為有用。引入此類中介服務時,需對定址與訊息篩選行為做相應調整。
本文深入探討如何使用中介服務(統稱路由器),介紹 WCF 定址與訊息篩選概念,著重路由器方案、路由配置與相應設定選項。第 2 部分將展示如何利用基礎原理實現更高階、更實用的路由功能。
參考 2007 年 6 月「服務站」Aaron Skonnard 專欄,WCF 處理邏輯/物理端點定址、定址標頭與訊息篩選。本節回顧基本定址功能及其對路由方案的影響。
通常客戶端使用從服務描述產生的 Proxy 直接發送訊息至目標服務。為相容,雙方共享等效約定與端點配置。以圖 1 服務約定與配置為例:
[ServiceContract(Namespace = "https://www.thatindigogirl.com/samples/2008/01")]
public interface IMessageManagerService {
[OperationContract] string SendMessage(string msg);
[OperationContract] void SendOneWayMessage(string msg);
}
<endpoint address="https://localhost:8000/MessageManagerService" contract="MessageManager.IMessageManagerService" binding="basicHttpBinding" />
三大關鍵定址需求:
https://.../IMessageManagerService/SendMessage。源自約定命名空間、約定名稱(預設介面名)、操作名稱(預設方法名)。.../SendMessageResponse。源自相同規則附加 "Response" 後綴。https://localhost:8000/MessageManagerService。源自端點 address 屬性(視為邏輯位址)。物理位址通常與邏輯位址匹配,客戶端發送至匹配 To 標頭的物理位址。服務中繼資料描述這些需求,客戶端產生相容 Proxy 與配置。客戶端端點配置範例:
<endpoint address="https://localhost:8000/MessageManagerService" binding="basicHttpBinding" contract="localhost.IMessageManagerService" name="basicHttp" />
Proxy 使用端點 address 作為邏輯與物理位址。呼叫 SendMessage 時發送含 To、Action 標頭的訊息。通道調度程序依 To 標頭匹配端點邏輯位址、依 Action 標頭匹配操作。

圖 4:透傳式路由器運作——客戶端關係對象為下游服務,訊息經路由器透傳

圖 5:處理式路由器運作——客戶端關係對象為路由器,路由器處理協定標頭並為下游服務建構新訊息
各配置皆有實用執行方法,亦可混合方案。
路由器接收下游服務訊息並轉發,亦負責接收服務回應返回客戶端。典型路由器約定展示處理任意訊息請求/回應的單一操作:
[ServiceContract(Namespace = "https://www.thatindigogirl.com/samples/2008/01")]
public interface IRouterService {
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message requestMessage);
}
預設 OperationContract 的 Action/ReplyAction 源自約定命名空間/名稱/操作名。通道調度程序需找到與 Action 標頭完全匹配的操作。設為 "*" 時,無論 Action 為何,未映射具體操作的所有訊息皆發送給全能操作。Action 或 ReplyAction 僅允許一個操作指定 "*"。
ProcessMessage 以 Message 類型收發非類型化訊息。路由器可存取標頭集合與訊息本文,除常用定址標頭(透過強類型屬性反序列化)外無自動序列化。基本路由器僅接收非類型化訊息並原樣轉發至下游服務,等待回應,回應同樣原樣轉發至呼叫客戶端。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class RouterService : IRouterService {
public Message ProcessMessage(Message requestMessage) {
using (ChannelFactory<IRouterService> factory = new ChannelFactory<IRouterService>("serviceEndpoint")) {
IRouterService proxy = factory.CreateChannel();
using (proxy as IDisposable) { return proxy.ProcessMessage(requestMessage); }
}
}
}
Proxy 通常強類型為目標服務約定,此處用路由器約定利用其轉發任意訊息能力。簡單範例中路由器僅原樣轉發訊息並返回回應。但有一個可能未預期的變更:訊息發送至服務前,"To" 標頭發生了改變。

圖 7:簡單路由器下的定址語義——To 標頭被代理端點邏輯位址覆蓋
預設代理使用其端點配置邏輯位址設定輸出訊息 To 標頭,即使傳入 Message 已有 To 標頭。雖看似好事(服務要求 To 匹配其端點邏輯位址),但若更新的 To 標頭未簽名且服務啟用安全性,訊息將被拒絕。
理想情況:客戶端發送匹配目標服務的 To 標頭,路由器接收並不改變 To 標頭轉發給服務。可透過繫結配置處理。
引入路由器時,理想狀態:客戶端使用正確服務 To 標頭發送訊息,又能將訊息發送至路由器。兩種主要方法:
<endpoint address="https://localhost:8000/MessageManagerService" binding="wsHttpBinding" ... behaviorConfiguration="viaBehavior"/> <behavior name="viaBehavior"> <clientVia viaUri="https://localhost:8010/RouterService"/> </behavior>
客戶端 Proxy 依端點邏輯位址生成 To 標頭,但透過路由器物理位址發送。缺點:將客戶端與路由器存在綁定。
<endpoint address="https://localhost:8010/RouterService" contract="..." binding="wsHttpBinding" listenUri="https://localhost:8000/MessageManagerService"/>
服務中繼資料發布路由器位址給客戶端,客戶端端點反映路由器位址。服務接收匹配其 listenUri 的 To 標頭,路由器先收到訊息。缺點:將服務與路由器綁定,理想是服務無需知曉。
<endpoint address="urn:MessageManagerService" contract="..." binding="wsHttpBinding" listenUri="https://localhost:8000/MessageManagerService"/>
服務使用不繫結路由器/服務的 URI 邏輯位址(如 urn:),手動通知客戶端物理位址(非中繼資料一部分)。兩種情況服務皆收到匹配端點配置的 To 標頭且路由器先收到訊息。
路由器應承擔配置重任,讓客戶端與服務不受其存在約束。 To 標頭絕不能匹配路由器邏輯位址。預設服務用 EndpointAddressMessageFilter 判斷 To 標頭匹配。路由器無法同樣操作,應安裝 MatchAllMessageFilter。透過 ServiceBehavior 的 AddressFilterMode 屬性設為 AddressFilterMode.Any 允許所有 To 標頭通過。
預設 To 標頭總根據代理端點配置更新。為抑制此行為讓路由器使用原始 To 標頭轉發,路由器繫結配置需啟用手動定址。標準繫結無法設定此屬性,必須使用自訂繫結:
<customBinding>
<binding name="manualAddressing">
<textMessageEncoding />
<httpTransport manualAddressing="true"/>
</binding>
</customBinding>

圖 9:啟用手動定址的路由器定址語義——標頭不改變
簡單路由實作僅在服務未啟用安全性、可靠會話或豐富協定時工作。圖 10 為簡化繫結協定視圖。

圖 10:服務約定與端點配置簡化視圖

圖 11:啟用安全性與可靠會話的透傳配置——協定特定訊息也透傳至服務
問題:服務發送/接收訊息包含接收方必須理解的標頭(MustUnderstand)。透傳路由器未啟用安全性/可靠會話,故無處理相關協定標頭的通道。
解決:將 ServiceBehavior 的 ValidateMustUnderstand=false 指示路由器忽略 MustUnderstand 標頭。但這僅保證來自客戶端的傳入訊息,來自下游服務的回應不在保證範圍。
完整解決:初始化呼叫下游服務的通道工廠時,加入行為指定 MustUnderstandBehavior(false):
factory.Endpoint.Behaviors.Add(new MustUnderstandBehavior(false));
現在協定與服務訊息可在客戶端與服務間自由透傳——假設使用 HTTP 協定。
若使用 TCP 或命名管道等雙向協定,出現另一複雜性:服務可向客戶端發起訊息(如啟用可靠會話時)。高階路由器配置可處理此特例,將在第 2 部分介紹。