官术网_书友最值得收藏!

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消息報頭。

AddressHeaderAddressHeaderCollection

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
            }
        }
主站蜘蛛池模板: 曲阜市| 辽阳县| 隆尧县| 运城市| 团风县| 平乡县| 呼玛县| 双辽市| 九江市| 库尔勒市| 东乌珠穆沁旗| 平度市| 株洲市| 兖州市| 普陀区| 砀山县| 黎城县| 灵丘县| 中宁县| 铁力市| 江孜县| 建始县| 乌拉特前旗| 镇康县| 长寿区| 泰兴市| 黑山县| 乌鲁木齐市| 岢岚县| 宁波市| 泸州市| 北海市| 奎屯市| 小金县| 潢川县| 广河县| 通辽市| 河西区| 连山| 洞口县| 义马市|