【WCF】自定义地址头的筛选器

作者: 梁丘刘峰

更新时间:2022-03-26 13:10:39

2303 阅读

前面的文章中,老周已向大伙伴们介绍了如何在终结点上使用地址头,只要服务是沿着该终结点调用的,那么每一次调用都会自动把地址头插入到SOAP消息的Header列表中。

而通过前一篇文章中的示例,大家也看到,客户端在调用服务时,必须指定与服务器完全一致的地址头,否则会验证失败。那是因为,在默认情况下,AddressFilter会对终结点的地址以及地址头进行校验。如果我们不希望使用默认校验行为,可以自定义一个MessageFiler,然后对传入的SOAP消息头进行验证。

MessageFilter是一个抽象类,它的结构如下:

    public abstract class MessageFilter
    {
         ……
        public abstract bool Match(MessageBuffer buffer);

        public abstract bool Match(Message message);
          ……
    }

我们重点要实现Match方法,如果校验成功,则返回true,如果不通过就返回false。Match方法有两个重载,我们核心要做的是处理参数类型为Message的版本,而参数类型为MessageBuffer的版本,只需要从buffer中读出一条SOAP消息,并传递给bool Match(Message message)方法即可。

下面代码演示该处理。

        public override bool Match(MessageBuffer buffer)
        {
            Message msg = buffer.CreateMessage();
            bool b= Match(msg);
            msg.Close();
            return b;
        }

CreateMessage方法从字节缓冲区生成一条消息实例,然后调用另一个Match方法,并得到验证结果,最后把结果返回即可。由于此处的Message是从buffer产生的临时消息实例,因此用完后,可以调用Close方法释放掉。

 

接下来,我们重点实现参数类型为Message的Match方法重载。本例子我主要验证客户端的地址头中是否包含一个名为vip的XML元素,命名空间为member-vip,并且,XML元素内有一个名为star的attribute。假设它表示VIP会员的星级,比如一个购书服务程序,不同星级的VIP可以获得不折扣的优惠。

即客户端在调用时应提供这样的地址头:

<vip xmlns="member-vip" star="2" />

咱们这个自定义MessageFilter的任务是检查消息头中是否存在vip元素,且命名空间为member-vip,包含star特性。

        public override bool Match(Message message)
        {
            var hd = message.Headers.FirstOrDefault(h => h.Namespace == HEADER_NS && h.Name == HEADER_ELNAME);
            if (hd == null) return false;

            XElement ele = message.Headers.GetHeader<XElement>(HEADER_ELNAME, HEADER_NS);

            if (ele.Attributes("star").Count() == 0)
            {
                return false;
            }
            return true;
        }

 

筛选器是在消息调度阶段执行的,负责对终结点进行调度的是EndpointDispatcher类,它有一个AddressFilter属性,引用的类型正是MessageFilter的派生类。故,我们只要把自定义的消息筛选器实例赋给AddressFilter属性即可,那么,如何赋值呢?

WCF为每个服务部分都提供了Behavior,不同的Behavior用于扩展不同的对象。比如,对服务本身,可以用Service Behavior来扩展;对于终结点,可以用Endpoint Behavior来扩展;对于服务协定,可以用Contract Behavior来扩展,等等。Behavior可以对各个对象的功能进行扩充,但在扩展时应当注意,扩展点最好与behavior相对应,即,如果扩展点是扩展终结点的行为的,就应该用Endpoint Behavior来扩展,而不要用Cannel Behavior来扩展。

AddressFilter是作用在终结点上的,所以,在扩展时应该实现IEndpointBehavior接口。不管是哪一种类型的behavior,通常我们在实现时,会实现以下两个方法:

ApplyDispatchBehavior——指的是behavior在服务器上被应用后的处理。

ApplyClientBehavior——指在客户端应用behavior后的处理。

 

AddressFilter只需要在服务器端进行处理,而不必考虑客户端,所以,重点实现ApplyDispatchBehavior方法即可。

    public class MyEndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
             
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
             
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.AddressFilter = new MyEndpointAddrFilter();
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            
        }
    }

很简单,直接把自定义的MessageFilter实例赋值给AddressFilter属性就可以了。Validate方法是用来验证当前的终结点是否合法,本例中不用进行检验,如果你验证的话可以写上相应的代码,如果终结点不合法,直接抛出异常就行了。

 

随后,把这个自定义的终结点behavior插入到服务的终结点中即可。

            using (ServiceHost host=new ServiceHost(typeof(SV)))
            {
                foreach (var svep in host.Description.Endpoints)
                {
                    if (svep.EndpointBehaviors.Contains(typeof(MyEndpointBehavior)) == false)
                    {
                        svep.EndpointBehaviors.Add(new MyEndpointBehavior());
                    }
                }

                host.Open();

 ………………

现在自定义的地址头筛选器已经起作用了。

 

目前这个Behavior不支持配置文件,只能使用代码来插入到ServiceEndpoint中。

要是希望该behavior可以通过配置文件使用,可以实现BehaviorExtensionElement抽象类。代码如下:

    public sealed class CustEndpointBehaviorElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get
            {
                return typeof(MyEndpointBehavior);
            }
        }

        protected override object CreateBehavior()
        {
            return new MyEndpointBehavior();
        }
    }

BehaviorType属性返回自定义behavior类型的Type,此处是MyEndpointBehavior类的Type。CreateBehavior方法返回自定义behavior的实例,此处当然是MyEndpointBehavior的实例了。

 

打开配置文件,在system.serviceModel节点下添加以下扩展声明:

  <system.serviceModel>
   <extensions>
      <behaviorExtensions>
        <add name="CustomEndpointBehavior" type="TestApp.CustEndpointBehaviorElement,TestApp"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>

name指定的是随后在配置文件中使用该扩展时的元素名称,type指定类型,类名要包含命名空间,逗号后面是程序集名称。

 

现在可以在behaviors节点下声明了。

  <behaviors>
      <endpointBehaviors>
        <behavior name="svepbhv">
          <CustomEndpointBehavior />
        </behavior>
      </endpointBehaviors>
    </behaviors>

behavior的节点名称就是刚才在behaviorExtensions / add 下指定的name值。

最后,记得在endpoint节点上引用behavior配置。

   <endpoint address="http://localhost:2288/demo" …… behaviorConfiguration="svepbhv"/>

 

好了,如此一来,自定义的地址头筛选方案就完成了。

看起来像是复杂一些,其实也没什么,总结起来就是:扩MessageFilter --> 扩Behavior --> 应用behavior,另外附加的就是实现BehaviorExtensionElement类,这只是为了让其支持配置文件。

 

本文示例代码下载地址

 

版权声明:本文著作权归作者【梁丘刘峰 】所有,不代表本网站立场。

侵权请联系:root_email@163.com

相关推荐