EndpointAddress——不隻是一個Uri[下篇]
《上篇》對AddressHeader在服務端和客戶端的作用,以及如何通過配置和編成的方式設置AddressHeader進行了詳細介紹。現在我們通過一個實例來演示終結點的地址報頭如何影響實現終結點選擇的消息篩選機製。這個實例通過為服務端終結點指定地址報頭實現針對客戶端的授權,讓經過許可的客戶端才能訪問這個服務。具體來說,我們將一個代碼序列號的GUID作為終結點的地址報頭。對於客戶端發送的消息,隻有具有相應的報頭才能訪問服務。[三個實例源代碼下載地址:實例1、實例2和實例3]
一、無地址報頭下服務調用(實例1)
我們采用計算服務的例子,整個實例的解決方案具有右圖所示的3個項目。其中類庫項目Service.Interface用於定義契約接口。Service項目是一個控製台應用程序,用於定義服務類型和作為服務的宿主。控製台應用程序Client代碼進行服務調用的客戶端。在本書後續部分的絕大部分實例都會采用這個結構。
實例演示的目的旨在旨在指導讀者編程,或者說明某個方麵的原理,所以我會將服務承載的業務功能盡量地簡化。所以我們分別在Service.Interface和Service項目中定義了如下所示的契約接口ICalculator和服務類型CalculatorService。ICalculator僅僅具有唯一的表示加法運算的Add操作。
ICalculator:
1: using System.ServiceModel;
2: namespace Artech.WcfServices.Service.Interface
3: {
4: [ServiceContract(Name = "CalculatorService", Namespace ="https://www.artech.com/")]
5: public interface ICalculator
6: {
7: [OperationContract]
8: double Add(double x, double y);
9: }
10: }
CalculatorService:
1: using Artech.WcfServices.Service.Interface;
2: namespace Artech.WcfServices.Service
3: {
4: public class CalculatorService : ICalculator
5: {
6: public double Add(double x, double y)
7: {
8: return x + y;
9: }
10: }
11: }
服務CalculatorService通過控製台程序Service進行寄宿。下麵是服務寄宿代碼和相應的配置。從配置可以看到,服務唯一的終結點具有一個作為地址報頭的<sn>元素,它的值代表服務的序列號。
服務寄宿程序:
1: using System;
2: using System.ServiceModel;
3: namespace Artech.WcfServices.Service
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
10: {
11: host.Open();
12: Console.Read();
13: }
14: }
15: }
16: }
配置:
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service name="Artech.WcfServices.Service.CalculatorService">
5: <endpoint address="https://127.0.0.1:3721/calculatorservice"
6: binding="ws2007HttpBinding"
7: contract="Artech.WcfServices.Service.Interface.ICalculator">
8: <headers>
9: <sn xmlns="https://www.artech.com/">
10: {DDA095DA-93CA-49EF-BE01-EF5B47179FD0}
11: </sn>
12: </headers>
13: </endpoint>
14: </service>
15: </services>
16: </system.serviceModel>
17: </configuration>
客戶端通過ChannelFactory<TChannel>創建的服務代理進行服務調用。下麵是進行服務調用的程序和客戶端配置。
服務調用程序:
1: using System;
2: using System.ServiceModel;
3: using Artech.WcfServices.Service.Interface;
4: namespace Artech.WcfServices.Client
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
11: {
12: ICalculator calculator = channelFactory.CreateChannel();
13: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2,calculator.Add(1,2));
14: }
15: Console.Read();
16: }
17: }
18: }
配置:
1: <configuration>
2: <system.serviceModel>
3: <client>
4: <endpoint name="calculatorservice"
5: address="https://127.0.0.1:3721/calculatorservice"
6: binding="ws2007HttpBinding"
7: contract="Artech.WcfServices.Service.Interface.ICalculator"/>
8: </client>
9: </system.serviceModel>
10: </configuration>
由於進行服務調用的客戶端終結點並沒有一個相應的表示序列號的<sn>地址報頭,在進行服務調用的時候沒有顯式地將序列號作為報頭添加到請求消息中,所以針對服務端來說,這是一個不被許可的客戶端。客戶端運行後將會拋出如下圖所示的EndpointNotFoundException異常。(S201)
二、為請求消息添加地址報頭(實例2)
假設服務端將作為序列化的GUID分發給經過許可的客戶端,那麼它就可以將其作為客戶端終結點的地址報頭定義到配置文件中,也可以在消息發送之前將序列化作為報頭添加到請求消息中。第一種方式比較簡單,我們來演示第二種方式。我們采用如下的代碼進行服務調用,在調用之前將序列號作為報頭添加到請求消息的報頭列表中。在這種情況下,服務嗲用將會順利進行。(S202)
1: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
2: {
3: ICalculator calculator = channelFactory.CreateChannel();
4: using (OperationContextScope contextScope = new OperationContextScope(calculator as IClientChannel))
5: {
6: string sn = "{DDA095DA-93CA-49EF-BE01-EF5B47179FD0}";
7: string ns = "https://www.artech.com/";
8: AddressHeader addressHeader = AddressHeader.CreateAddressHeader("sn", ns, sn);
9: MessageHeader messageHeader = addressHeader.ToMessageHeader();
10: OperationContext.Current.OutgoingMessageHeaders.Add(messageHeader);
11: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
12: }
13: }
輸出結果:
1: x + y = 3 when x = 1 and y = 2
之所以在請求消息中不存在於終結點地址報頭相匹配的報頭會導致拋出EndpointNotFoundException異常,原因在於按照默認的消息篩選機製找不到匹配的終結點。為了解決這個問題,對於客戶端來說,可以通過在消息中添加相應的報頭滿足服務端篩選的條件;而對於服務端來說,則可以改變為了實現終結點的選擇而采用消息篩選機製。總之一句話,隻要服務端能夠根據匹配的終結點就可以抑製EndpointNotFoundException異常的拋出。
三、改變地址篩選策略(實例3)
我們可以在服務類型上應用ServiceBehaviorAttribute特性並為AddressFilterMode屬性進行相應的設置來改變針對終結點地址的篩選機製。如下麵的代碼所示,AddressFilterMode屬性是一個類型為AddressFilterMode的枚舉。三個枚舉項(Exact、Prefix和Any)分別代表三種地址匹配的策略,即精確匹配,基於前綴匹配和匹配任意地址。
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: [DefaultValue(0)]
5: public AddressFilterMode AddressFilterMode { get; set; }
6: }
7: public enum AddressFilterMode
8: {
9: Exact,
10: Prefix,
11: Any
12: }
其中Exact和Prefix都需要進行地址報頭的匹配,而Any則不需要。從應用在AddressFilterMode的DefaultValueAttribute特性可以看出,該屬性的默認值是Exact,所以在默認的情況下采用的是針對地址的精確匹配。那麼如果我們在CalculatorService上應用ServiceBehaviorAttribute特性並將AddressFilterMode設置為Any,即使請求消息中不具有相關的報頭,服務調用也會成功。(S203)
1: [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
2: public class CalculatorService : ICalculator
3: {
4: //省略成員
5: }
本例雖然名為通過“通過地址報頭實現對客戶端的授權”,其實在真正的應用中我們不會通過這樣的方式對服務授權。因為終結點的地址報頭是元數據的一部分,客戶端在獲取服務發布的元數據時會將地址報頭一並獲取。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 14:34:32