《上篇》对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 ="http://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="http://127.0.0.1:3721/calculatorservice"
   6:                         binding="ws2007HttpBinding"                        
   7:                         contract="Artech.WcfServices.Service.Interface.ICalculator">
   8:                 <headers>
   9:                   <sn xmlns="http://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="http://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 = "http://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: }

本例虽然名为通过“通过地址报头实现对客户端的授权”,其实在真正的应用中我们不会通过这样的方式对服务授权。因为终结点的地址报头是元数据的一部分,客户端在获取服务发布的元数据时会将地址报头一并获取。

EndpointAddress——不只是一个Uri[上篇]
EndpointAddress——不只是一个Uri[下篇]

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

EndpointAddress——不只是一个Uri[下篇]相关推荐

  1. URI、URL以及URN的区别

    首先,URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源.而URL是uniform resource locator,统一资源定位器,它是一种具体 ...

  2. 花了一个星期,我终于把RPC框架整明白了!

    " RPC(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想. 作者:李金葵,来自:51CTO技术栈 R ...

  3. URL/URI/URN的认识

    2019独角兽企业重金招聘Python工程师标准>>> 这两天在写代码的时候,由于涉及到资源的位置,因此,需要在Java Bean中定义一些字段,用来表示资源的位置,比如:imgUr ...

  4. WINDOWSPHONE STUDY1:创建一个 Windows Phone 7 下的简单 RSS 阅读器

    在这篇文章中我们将为 Windows Phone 7 手机创建一个简单的 RSS (Really Simple Syndication) 阅读器.用户界面包含一个文本输入框用于输入 RSS 地址,以及 ...

  5. 【网络基础】URI 和 URL 的纠缠

    名词解释: URI (uniform resource identifier) 统一资源标志符: URL(uniform resource location ) 统一资源定位符(或统一资源定位器): ...

  6. URI,URL和URN有什么区别?

    人们谈论URL , URI和URN好像是不同的东西,但是用肉眼看起来都是一样的. 它们之间有什么明显区别? #1楼 这是我的简化: URN:唯一资源名称,即" what"(例如ur ...

  7. URI URL 简介区别

    一.URI <1>什么是URI URI,通一资源标志符(Uniform Resource Identifier, URI),表示的是web上每一种可用的资源,如 HTML文档.图像.视频片 ...

  8. Android中的URI

    就Android平台而言,URI主要分三个部分:scheme, authority and path.其中authority又分为host和port.格式如下: scheme://host:port/ ...

  9. Android 中ContentProvider和Uri详解

    一.使用ContentProvider(内容提供者)共享数据 ContentProvider在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给 ...

最新文章

  1. Python 3 利用 Dlib 实现人脸检测和剪切
  2. ZOJ 3471 压缩状态DP
  3. linux删除副本文件,RMAN不能删除控制文件副本解决
  4. Spring Boot动态修改日志级别
  5. 【五级流水线CPU】—— 3. 逻辑、移位与空指令 + 数据冒险RAM解决
  6. 周日慕田峪生鱼片之旅,失败的第一次出台
  7. Android handler.obtainMessage()传字符串用法
  8. jquery 学习笔记(二)
  9. hive变量传递的源码实现
  10. 使用windows自带的网络命令工具抓包
  11. 当你写爬虫抓不到APP请求包的时候该怎么办?【高级篇-混淆导致通用Hook工具失效】
  12. 一文了解无聊猿(BAYC)的 NFT 帝国是如何形成的
  13. nginx设置禁止访问某个页面及只允许域名访问或某个ip禁止访问
  14. 在私有云上创建虚拟机
  15. Codeforces Round #495 C. Sonya and Robots
  16. Fitbit被谷歌21亿美元收购 品牌启用英文域名Fitbit.com
  17. 大数据从入门到实战 - 第3章 MapReduce基础实战
  18. RocketMQ集群安装部署(多主多从)
  19. 分享6个国内优秀Java后台管理框架的开源项目
  20. html css点击展开列表,html+js+css实现点击展开显示

热门文章

  1. 怎么去掉视频上的水印?快速去除水印或字幕的大神技巧
  2. 作为学生借助 CODECHINA 这样学习 Git 知识
  3. 7天内完成基础USB开发(2)——Pionway SDK简介
  4. js实现返回上一页(页面刷新与不刷新)
  5. php 将中文字符转英文字母_PHP实现将汉字转换为拼音及获取词语首字母的方法...
  6. 中国商业重点产品追溯管理平台
  7. 中国偶氮二异丁腈市场供需动态与投资前景展望报告(新版)2022-2027年
  8. 看这篇就够了——ubuntu系统中的cuda cudnn cudatookit及pytorch使用
  9. 本科、硕士和博士有何区别?
  10. Jupyter notebook用谷歌浏览器打开