前面的文章中,老周已向大伙伴们介绍了如何在终结点上使用地址头,只要服务是沿着该终结点调用的,那么每一次调用都会自动把地址头插入到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类,这只是为了让其支持配置文件。