WCF 扩展之我见: MessageFilter

在 System.ServiceModel.Dispatcher 名称空间下,WCF 为我们提供了8种预置的 Filter:

ActionMessageFilter: 根据 Action 来过滤

EndpointAddressMessageFilter: 根据 action 的 to 地址 和 消息报头 来过滤

EndpointNameMessageFilter: 根据 Endpoint 名字来过滤

MatchAllMessageFilter: 匹配任何 Message,相当于没有过滤

MatchNoneMessageFilter: 不匹配任何 Message

PrefixEndpointMessageFilter: 与 EndpointMessageFilter 类似,不过只匹配 to 地址的前缀

StrictAndMessageFiler: 表示 "与" 操作,只有当 And 双方的 Filter 都满足时才满足

XPathMessageFilter: 使用 XPath 表达式来过滤


说到 MessageFitler,不得不扯一下 WCF 的寻址问题。从《消息处理流程》一文中可以知道在 Message 进入 MessagePump 之后的首要任务就是确定 EndpointDispatcher,而 MessageFitler 就是用来完成此事。


作 用

由于 MessageFilter 处于 ServiceModelLayer 的最前面,它直接决定了一个 Message 最终会由哪个 Service 进行处理,因此,通过自定义 MessageFilter 便可以根据 Message 所包含的信息(一般是 MessageHeader)进行过滤,从而决定一个 Message 是否可以由某个 Service 进行处理。最常见的使用场景便是路由服务。


原理解析

WCF 内部会遍历当前 ListenUri 下的每个 Endpoint,然后用 AddressFilter (默认为 EndpointAddressMessageFilter) 和 ContractFilter(默认为 ActionMessageFilter) 对 Endpoint 的地址进行过滤,最终找到目标 Endpoint 所对应的 EndpointDispatcher。


EndpointAddressMessageFilter

来看一下源代码

public override bool Match(Message message)
{
    if (message == null)
    {
        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
    }
 
    // To
    Uri to = message.Headers.To;
    
    //address 是当前Endpoint的地址
    Uri actingAs = this.address.Uri;
 
    if (to == null || !this.comparer.Equals(actingAs, to))
    {
        return false;
    }
 
    return this.helper.Match(message);
}


//helper.Match
internal bool Match(Message message)
{
    if (this.size == 0)
    {
        return true;
    }
 
    EndpointAddressProcessor context = CreateProcessor(this.size);
    
    //对 header 进行处理
    context.ProcessHeaders(message, this.qnameLookup, this.headerLookup);
    bool result = context.TestExact(this.mask);
    ReleaseProcessor(context);
    return result;
}

从中可以看到当与某个 Endpoint 比较时, AddressFilter 会比较 action 的 To 地址,并且对 MessageHeader 进行匹配,只有完全一致的地址 Match 才会成功。


来看一个 Endpoint 的配置实例

<services>
  <service name="WcfService1.Service1" >
	<host>
	  <baseAddresses>
		<add baseAddress="http://localhost:8888/"/>
	  </baseAddresses>
	</host>
	<endpoint address="myservice" binding="basicHttpBinding" contract="WcfService1.IService1" name="ep">
	  <headers>
            <myParameter>xxx</myParameter>            
          </headers>
	</endpoint>
  </service>
</services>

ep 的地址为 "http://localhost:8888/myservice", 如果没有特别指定,默认情况下 ListenUri 就是 Endpoint 的 address。如果想要通过 EndpointAddressMessageFilter 的检查,则客户端在发起请求的时候,除了地址匹配之外,请求中的 endpoint 必须携带名叫 myParamter,值为 xxx 的 header。


补充几个小知识:

1,当 Contract 不同,Binding 相同,Address 可以是同一个 (即,schema://host:port) 。

2,AddressHeader 中的内容最终会被转变成 MessageHeader 中的报头,用于在匹配地址时使用(客户端和服务端必须具有相同的报头才能通信)。


ActionMessageFilter

同样,先来看一下源代码:

bool InnerMatch(Message message)
{
	string act = message.Headers.Action;
	if (act == null)
	{
		act = string.Empty;
	}

	return this.actions.ContainsKey(act);
}

public override bool Match(Message message)
{
	if (message == null)
	{
		throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
	}

	return InnerMatch(message);
}

this.actions 是某个 Endpoint 所对应 Contract 中的所有 operation。ActionMessageFilter 会检查该 Endpoint 中是否存在一个 action 与message 中的 action 一致。

默认情况下,一个 Operation 对应的 action = namespace/contract name/operation name,(e.g. http://tempuri.org/IService1/GetData)。  这个值可以通过修改 OperationContract 的 Action 属性来变更。


AddressFilterMode

ServiceBehavior 中有一个参数用于指定 AddressFilter 的模式(Exact、Prefix 和 Any),不同的模式会使用 AddressFilter 采用不同的方式进行过滤,下面是在执行 ServiceBehavior.ApplyDispatchBehavior 时所执行的代码。

if (!endpointDispatcher.AddressFilterSetExplicit)
{
	EndpointAddress address = endpointDispatcher.OriginalAddress;
	if (address == null || this.AddressFilterMode == AddressFilterMode.Any)
	{
		endpointDispatcher.AddressFilter = new MatchAllMessageFilter();
	}
	else if (this.AddressFilterMode == AddressFilterMode.Prefix)
	{
		endpointDispatcher.AddressFilter = new PrefixEndpointAddressMessageFilter(address);
	}
	else if (this.AddressFilterMode == AddressFilterMode.Exact)
	{
		endpointDispatcher.AddressFilter = new EndpointAddressMessageFilter(address);
	}
}


如何扩展 MessageFilter

上面介绍了预置的几类 Filter,本节来介绍下如何自定义一个 MessageFilter,并如何将其应用到 WCF 应用程序中。


首先,继承自 MessageFilter,并根据业务需求对两个重载的 Match 进行编码。

public class MyMessageFilter : MessageFilter
{
    public override bool Match(System.ServiceModel.Channels.Message message)
    {
        return true;
    }

    public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
    {
        return true;
    }
}


使用合适的 Behavior 把自定义的 Filter 添加到 WCF 的扩展点中。

查看《WCF 扩展之我见: Behaviors》 一文,便会发现 MessageFilter 是 EndpointDispatcher 的一个属性,如果我们只想应用于某个 Endpoint,则可以通过自定义一个 EndpointBehavior 来实现:

class MyFilterBehavior :Attribute, IEndpointBehavior
{
    public MyFilterBehavior()
    {

    }
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.AddressFilter = new MyMessageFilter();
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}


后续步骤,请根据 《WCF 扩展之我见: Behaviors》 一文的 <如何应用自定义 Behavior> 一节进行操作。

参考资料

Custom Message Filter

WS-Addressing Message Addressing Properties

OperationContractAttribute.Action Property

文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charley Box | 关于本站 | 浙ICP备13014059号