- WCF技術(shù)剖析(卷1)
- 蔣金楠
- 2156字
- 2018-12-27 11:32:39
2.1.3 如何指定AddressHeader
WCF是一個基于消息的通信框架,具有一個消息處理的管道。消息流到該管道不同的位置,要對消息進行相應(yīng)的處理。雖然基于REST/POX的Web Service越來越盛行,但是基于SOAP的Web Service還是主流。SOAP已經(jīng)成為了Web Service事實上的標準,SOAP同時也是整個WS-*體系結(jié)構(gòu)的基礎(chǔ)。得益于可擴展的SOAP消息結(jié)構(gòu):SOAP報頭+消息主體,我們可以把各種各樣的控制信息置于SOAP封套(Envelope)的報頭集合中,各種WS-* 協(xié)議(比如WS-Addressing、WS-Reliable Messaging、WS-Security等)通過定義各自的SOAP報頭得以實現(xiàn)。
對于基于消息的通信方式來說,尋址(Addressing)是需要首先解決的問題。一般情況下,WCF通過WS-Addressing規(guī)定的標準解決尋址的問題。也就是說通過WS-Addressing標準的SOAP報頭內(nèi)容對目標地址進行解析。典型的尋址相關(guān)的報頭包含:<wsa:To>、<wsa:Via>、<wsa:ReplyTo>等。
但是在某些情況下,默認的這種尋址方式不能完全解決現(xiàn)實中的具體需求,我們需要一種不同的方式對消息進行路由,在這種情況下,需要實現(xiàn)手工尋址(Manual Addressing)。對于實現(xiàn)手工尋址所需相關(guān)的輔助信息,需要定義在SOAP消息的報頭中。WCF通過一個特殊的對象,AddressHeader來表示這種尋址相關(guān)的SOAP消息報頭。
AddressHeader和AddressHeaderCollection
AddressHeader定義在System.ServiceModel.Channels命名空間下,表示用于消息尋址相關(guān)的信息的報頭,下面是AddressHeader的簡單定義:
public abstract class AddressHeader { //其他成員 public static AddressHeader CreateAddressHeader(object value); public static AddressHeader CreateAddressHeader(object value, XmlObjectSerializer serializer); public static AddressHeader CreateAddressHeader(string name, string ns, object value); public T GetValue<T>(); public T GetValue<T>(XmlObjectSerializer serializer); public MessageHeader ToMessageHeader(); public void WriteAddressHeader(XmlDictionaryWriter writer); public void WriteAddressHeader(XmlWriter writer); public void WriteAddressHeaderContents(XmlDictionaryWriter writer); public void WriteStartAddressHeader(XmlDictionaryWriter writer); public abstract string Name { get; } public abstract string Namespace { get; } }
通過AddressHeader的靜態(tài)方法CreateAddressHeader可以很方便地創(chuàng)建AddressHeader對象;GetValue方法用于獲取AddressHeader的值;通過ToMessageHeader可以將AddressHeader轉(zhuǎn)化為MessageHeader對象,而MessageHeader對象表示的就是一個消息的報頭。4個WriteXXX方法將AddressHeader寫入一個流或具體的文件中。Name和Namespace屬性代表的是報頭的名稱和命名空間。
除了AddressHeader類型,在System.ServiceModel.Channels命名空間下還定義了另一個與之相關(guān)的對象AddressHeaderCollection,它表示AddressHeader的集合。AddressHeaderCollection繼承自ReadOnlyCollection<AddressHeader>,所以該集合是只讀的。
public sealed class AddressHeaderCollection:ReadOnlyCollection<AddressHeader> { //其他成員 public AddressHeaderCollection(); public AddressHeaderCollection(IEnumerable<AddressHeader> addressHeaders); public void AddHeadersTo(Message message); public AddressHeader[] FindAll(string name, string ns); public AddressHeader FindHeader(string name, string ns); }
通過AddHeadersTo方法可以很容易地將一個AddressHeaderCollection對象添加到一個Message對象的報頭列表中;FindAll和FindHeader根據(jù)報頭的名稱和命名空間找到對應(yīng)的AddressHeader。FindAll得到所有相關(guān)的AddressHeader,而FindHeader只獲得滿足條件的第一個AddressHeader。
再回到EndpointAddress的定義,可以看到Header屬性的類型就是AddressHeaderCollection,不過這僅僅是一個只讀的屬性,我們不能通過該屬性為EndpointAddress添加AddressHeader。接下來介紹如何添加AddressHeader對象。
public class EndpointAddress { //其他成員 public Uri Uri { get; } public AddressHeaderCollection Headers { get; } public EndpointIdentity Identity { get; } }
如何為服務(wù)指定AddressHeader
在對服務(wù)寄宿的時候,可以通過代碼和配置的方式為EndpointAddress添加相應(yīng)的AddressHeader。下面的代碼片斷演示了在對服務(wù)進行寄宿的過程中如何設(shè)定AddressHeader。
using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService))) { string headerValue = "Licensed User"; string headerName = "UserType"; string headerNamespace = "http://www.artech.com/"; AddressHeader addressHeader = AddressHeader.CreateAddressHeader (headerName,headerNamespace,headerValue); EndpointAddress endpointAddress = new EndpointAddress(new Uri ("http://127.0.0.1:9999/CalculatorService"),addressHeader); ServiceEndpoint serviceEndpoint = new ServiceEndpoint (ContractDescription.GetContract(typeof(ICalculator)), new BasicHttpBinding(),endpointAddress); serviceHost.Description.Endpoints.Add(serviceEndpoint); serviceHost.Open(); //其他代碼 }
上面的事例代碼中,為了確定訪問者的類型:購買了服務(wù)的用戶(Licensed user)和免費試用的用戶(Trivial user),添加了一個名稱為UserType的AddressHeader,命名空間為http://www.artech.com/。我們希望該終結(jié)點只能讓第一類用戶進行訪問,將AddressHeader的值設(shè)為Licensed User。先通過AddressHeader的靜態(tài)方法CreateAddressHeader創(chuàng)建了一個AddressHeader對象,然后傳入EndpointAddress的構(gòu)造函數(shù)創(chuàng)建EndpointAddress。最后基于此EndpointAddress創(chuàng)建終結(jié)點對象,并添加到服務(wù)的終結(jié)點列表中。
在一般情況下,我們傾向于通過配置的方式來指定終結(jié)點的AddressHeader列表。AddressHeader定義在終結(jié)點<headers>配置項中,直接以XML的方式指定名稱、命名空間和值。下面的一段配置和上面的代碼是等效的。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="Artech.WcfServices.Services.CalculatorService"> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="basicHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator"> <headers> <UserType xmlns="http://www.artech.com/">Licensed User</UserType> </headers> </endpoint> </service> </services> </system.serviceModel> </configuration>
如何在客戶端指定AddressHeader
對于客戶端來說,同樣具有兩種不同的方式對AddressHeader進行設(shè)定:代碼的方式和配置的方式。和在服務(wù)端的配置相似,客戶端終結(jié)點的AddressHeader列表也定義在終結(jié)點的<headers>配置項中。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint name="CalculatorService" address= "http://127.0.0.1:8888/CalculatorService" binding= "basicHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" > <headers> <UserType xmlns="urn:artech.com">Licensed User</UserType> </headers> </endpoint> </client> </system.serviceModel> </configuration>
如果采用配置的方式添加了上述AddressHeader,當我們采用該AddressHeader所在的終結(jié)點進行服務(wù)調(diào)用時,相應(yīng)的MessageHeader將會自動附加到請求消息中,代碼如下所示。
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <UserType xmlns="http://www.artech.com/">Licensed User</UserType> </s:Header> <s:Body> ... ... </s:Body> </s:Envelope>
如果不希望每次服務(wù)訪問的消息都是自動附加上這么一段SOAP報頭(可能該報頭僅僅在某些確定的服務(wù)調(diào)用下才需要),在這種情況下,我們可以通過當前OperationContext的OutgoingMessageHeaders屬性(OperationContext.Current.OutgoingMessageHeaders),直接將AddressHeader添加到出棧消息報頭列表中,代碼如下所示。
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("CalculatorService")) { ICalculator calculator = channelFactory.CreateChannel(); using (calculator as IDisposable) { using (OperationContextScope contextScope = new OperationContextScope(calculator as IContextChannel)) { string headerValue = "Licensed User"; string headerName = "UserType"; string headerNamespace = "http://www.artech.com"; AddressHeader addressHeader = AddressHeader.CreateAddressHeader (headerName, headerNamespace, headerValue); OperationContext.Current.OutgoingMessageHeaders.Add( addressHeader.ToMessageHeader()); Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1,2, calculator.Add(1,2)); } } }
由于最終的實現(xiàn)是將MessageHeader添加到OutgoingMessageHeaders中,所以也可以繞開AddressHeader,直接添加一個與之等效的MessageHeader。
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("CalculatorService")) { ICalculator calculator = channelFactory.CreateChannel(); using (calculator as IDisposable) { using (OperationContextScope contextScope = new OperationContextScope(calculator as IContextChannel)) { string headerValue = "Licensed User"; string headerName = "UserType"; string headerNamespace = "http://www.artech.com";
MessageHeader<string> messageHeader = new MessageHeader<string>(headerValue); OperationContext.Current.OutgoingMessageHeaders.Add (messageHeader.GetUntypedHeader(headerName,headerNamespace)); Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1,2, calculator.Add(1,2)); } } }
上面采用ChannelFactory<T>的方式編寫了客戶端的代碼并進行了服務(wù)調(diào)用。當我們使用繼承自ClientBase<T>的服務(wù)代理進行服務(wù)調(diào)用時,AddressHeader的指定方式具有小小的差異。
using (CalculatorClient CalculatorClient = new CalculatorClient("CalculatorService")) { using (OperationContextScope contextScope = new OperationContextScope (CalculatorClient.InnerChannel as IContextChannel)) { string headerValue = "Licensed User"; string headerName = "UserType"; string headerNamespace = "http://www.artech.com "; AddressHeader addressHeader = AddressHeader.CreateAddressHeader (headerName, headerNamespace, headerValue); OperationContext.Current.OutgoingMessageHeaders.Add (addressHeader.ToMessageHeader()); Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, CalculatorClient.Add(1, 2)); } }
由于在服務(wù)端我們?yōu)榉?wù)的終結(jié)點指定了AddressHeader,就意味著該終結(jié)點只接受消息的報頭和與此AddressHeader相匹配的消息請求。我們可以通過實驗證實這一點:通過下面的配置,我們將客戶端AddressHeader的值從服務(wù)端希望的Licensed User變成Trivial User。由于服務(wù)端找不到相匹配的終結(jié)點導(dǎo)致如圖2-3所示的EndpointNotFoundException異常被拋出。

圖2-3 基于AddressHeader的消息篩選導(dǎo)致EndpointNotFoundException異常
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <endpoint name="CalculatorService" address= "http://127.0.0.1:8888/CalculatorService" binding= "basicHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" > <headers> <UserType xmlns="urn:artech.com">Trivial User </UserType> </headers> </endpoint> </client> </system.serviceModel> </configuration>
本例實際上涉及的是消息篩選(Message Filter)問題,消息篩選解決的是如何通過接收的消息選擇相應(yīng)終結(jié)點的問題。其中地址篩選(Address Filter)是消息篩選的一種類型,WCF默認采用完全地址匹配的模式:地址的URI和AddressHeader均完全匹配。為了讓服務(wù)端的終結(jié)點在AddressHeader和消息的報頭不匹配的情況下也能處理消息請求,我們可以通過ServiceBeahviorAttribute改變地址篩選模式(AddressFilterMode)。比如在下面的代碼中,通過ServiceBehaviorAttribute將AddressFilterMode設(shè)為AddressFilterMode.Any,從而避免了如圖2-3所示的EndpointNotFoundException異常的拋出。關(guān)于消息篩選,在本章2.4節(jié)中還會詳細介紹。
namespace Artech.WcfServices.Services { [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)] public class CalculatorService:ICalculator { #region ICalculator Members public double Add(double x, double y) { return x + y; } #endregion } }
- Python程序設(shè)計(第3版)
- Practical DevOps
- Swift 3 New Features
- Learning ELK Stack
- 3D少兒游戲編程(原書第2版)
- Hands-On Swift 5 Microservices Development
- C++從入門到精通(第5版)
- Advanced Express Web Application Development
- Canvas Cookbook
- 深入理解C指針
- Java程序設(shè)計教程
- Python大規(guī)模機器學(xué)習(xí)
- Python高性能編程(第2版)
- 編程風(fēng)格:程序設(shè)計與系統(tǒng)構(gòu)建的藝術(shù)(原書第2版)
- 深度學(xué)習(xí)的數(shù)學(xué):使用Python語言