按正文元素调度

AdvancedDispatchByBody 示例演示如何实现一种用于将传入消息分配到操作的替代算法。

默认情况下,服务模型调度程序根据消息的 WS-Addressing“作”标头或 HTTP SOAP 请求中的等效信息为传入消息选择适当的处理方法。

一些不遵循 WS-I 基本配置文件 1.1 准则的 SOAP 1.1 Web 服务堆栈不是根据 Action URI 调度消息,而是根据 SOAP 消息体中第一个元素的 XML 限定名称进行调度。 同样,这些协议栈的客户端可能会发送带有空的或任意的 HTTP SoapAction 标头的消息,这是 SOAP 1.1 规范所允许的。

为了更改消息调度到方法的方式,该示例在IDispatchOperationSelector上实现DispatchByBodyElementOperationSelector扩展性接口。 此类根据消息正文的第一个元素来选择操作。

类构造函数需要一个用 XmlQualifiedName 和字符串对填充的字典,其中限定名称指示 SOAP 正文的第一个子级的名称,而字符串指示匹配的操作名称。 defaultOperationName 是操作的名称,该操作接收无法与此字典匹配的所有消息:

class DispatchByBodyElementOperationSelector : IDispatchOperationSelector
{
    Dictionary<XmlQualifiedName, string> dispatchDictionary;
    string defaultOperationName;

    public DispatchByBodyElementOperationSelector(Dictionary<XmlQualifiedName,string> dispatchDictionary, string defaultOperationName)
    {
        this.dispatchDictionary = dispatchDictionary;
        this.defaultOperationName = defaultOperationName;
    }
}

IDispatchOperationSelector实现非常简单,因为接口上只有一种方法: SelectOperation 此方法的作业是检查传入消息,并返回一个字符串,该字符串等于当前终结点的服务协定中方法的名称。

在此示例中,操作选择器使用 XmlDictionaryReader 获取传入消息正文的 GetReaderAtBodyContents。 此方法已将阅读器定位到消息正文的第一个子节点,这样就可以获取当前元素的名称和命名空间 URI,并将它们合并为一个 XmlQualifiedName,然后用于从操作选择器持有的字典中查找相应的操作。

public string SelectOperation(ref System.ServiceModel.Channels.Message message)
{
    XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
    XmlQualifiedName lookupQName = new
       XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
    message = CreateMessageCopy(message,bodyReader);
    if (dispatchDictionary.ContainsKey(lookupQName))
    {
         return dispatchDictionary[lookupQName];
    }
    else
    {
        return defaultOperationName;
    }
}

使用 GetReaderAtBodyContents 或提供对邮件正文内容访问权限的任何其他方法访问消息正文会导致消息标记为“read”,这意味着消息对于任何进一步处理都无效。 因此,作选择器使用以下代码所示的方法创建传入消息的副本。 由于读取者的位置在检查过程中尚未更改,因此也可以由新创建的消息引用,消息属性和消息标头也会复制到其中,这会导致原始消息的确切克隆:

private Message CreateMessageCopy(Message message,
                                     XmlDictionaryReader body)
{
    Message copy = Message.CreateMessage(message.Version,message.Headers.Action,body);
    copy.Headers.CopyHeaderFrom(message,0);
    copy.Properties.CopyProperties(message.Properties);
    return copy;
}

向服务添加操作选择器

服务调度作业选择器是 Windows Communication Foundation (WCF) 调度程序的扩展。 在双工协定回调通道上选择方法时,还可以使用客户端操作选择器,其工作方式类似于本文说明的调度操作选择器,但未显式包括在此示例中。

和多数服务模型扩展一样,调度操作选择器通过使用行为来添加到调度程序。 行为是一个配置对象,它向调度运行时(或客户端运行时)添加一个或多个扩展,或者更改其设置。

由于操作选择器具有协定范围,因此要实现的相应行为是 IContractBehavior。 由于接口在派生类上 Attribute 实现,如以下代码所示,因此可以声明性地将该行为添加到任何服务协定中。 每当打开 ServiceHost 并生成调度运行时时,都会自动添加作为协定、操作和服务实现上的属性的所有行为或作为服务配置中的元素的所有行为,随后要求这些行为提供扩展或修改默认设置。

为了简洁起见,以下代码摘录仅显示方法 ApplyDispatchBehavior的实现,这会影响此示例中调度程序的配置更改。 因为它们直接返回给调用方而不执行任何工作,因此没有显示其他方法。

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
class DispatchByBodyElementBehaviorAttribute : Attribute, IContractBehavior
{
    // public void AddBindingParameters(...)
    // public void ApplyClientBehavior(...)
    // public void Validate(...)

首先,ApplyDispatchBehavior 实现通过遍历服务终结点 OperationDescription 中的 ContractDescription 元素,为操作选择器设置查找字典。 然后,检查每个操作描述中是否存在 DispatchBodyElementAttribute 行为,这种行为的实现也在本示例中定义为 IOperationBehavior。 虽然此类也是一种行为,但它是被动的,并且不会主动为调度运行时提供任何配置更改。 所有方法均返回给调用方,不进行任何操作。 操作行为仅存在,是为了将新调度机制所需的元数据,即选择操作所依据的正文元素的限定名称,与相应的操作相关联。

如果找到此类行为,则会将由 XML 限定名称(QName 属性)和操作名称(Name 属性)创建的值对添加到字典中。

填充字典后,将使用此信息构造一个新 DispatchByBodyElementOperationSelector 项,并将其设置为调度运行时的作选择器:

public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
    Dictionary<XmlQualifiedName,string> dispatchDictionary =
                     new Dictionary<XmlQualifiedName,string>();
    foreach( OperationDescription operationDescription in
                              contractDescription.Operations )
    {
        DispatchBodyElementAttribute dispatchBodyElement =
   operationDescription.Behaviors.Find<DispatchBodyElementAttribute>();
        if ( dispatchBodyElement != null )
        {
             dispatchDictionary.Add(dispatchBodyElement.QName,
                              operationDescription.Name);
        }
    }
    dispatchRuntime.OperationSelector =
            new DispatchByBodyElementOperationSelector(
               dispatchDictionary,
               dispatchRuntime.UnhandledDispatchOperation.Name);
    }
}

服务的实现

此示例中实现的行为直接影响如何解释和调度来自线路的消息,这是服务协定的一项功能。 因此,应在选择使用它的任何服务实现中的服务协定级别上声明该行为。

示例项目服务将DispatchByBodyElementBehaviorAttribute协定行为应用于IDispatchedByBody服务协定,并将这两个操作OperationForBodyA()OperationForBodyB()标记为一个DispatchBodyElementAttribute操作行为。 当承载实现此契约的服务的服务宿主被打开时,如前所述,调度器构建器会获取这些元数据。

由于操作选择器只根据消息正文元素进行调度并忽略“Action”,因此需要通过将通配符“*”分配给 ReplyActionOperationContractAttribute 属性来告诉运行时不要对返回的答复检查“Action”标头。 此外,需要有一个默认操作,该操作将“Action”属性设置为通配符“*”。 此默认操作接收所有无法调度的消息并且没有 DispatchBodyElementAttribute

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples"),
                            DispatchByBodyElementBehavior]
public interface IDispatchedByBody
{
    [OperationContract(ReplyAction="*"),
     DispatchBodyElement("bodyA","http://tempuri.org")]
    Message OperationForBodyA(Message msg);
    [OperationContract(ReplyAction = "*"),
     DispatchBodyElement("bodyB", "http://tempuri.org")]
    Message OperationForBodyB(Message msg);
    [OperationContract(Action="*", ReplyAction="*")]
    Message DefaultOperation(Message msg);
}

示例服务实现非常简单。 每个方法都会将收到的消息包装到回复消息中,并将其回显给客户端。

运行和生成示例

运行示例时,操作响应的正文内容将显示在客户端控制台窗口中,类似于以下格式化的输出。

客户端向服务发送三条消息,其正文内容元素分别命名为bodyAbodyBbodyX。 从前面的说明和服务协定中可以推断,具有 bodyA 元素的传入消息将调度到 OperationForBodyA() 方法。 由于消息没有具有bodyX正文元素的显式调度目标,因此将消息调度到DefaultOperation()。 每个服务作都会将收到的消息正文包装到特定于方法的元素中并返回它,这是为了清楚地关联此示例的输入和输出消息:

<?xml version="1.0" encoding="IBM437"?>
<replyBodyA xmlns="http://tempuri.org">
   <q:bodyA xmlns:q="http://tempuri.org">test</q:bodyA>
</replyBodyA>
<?xml version="1.0" encoding="IBM437"?>
<replyBodyB xmlns="http://tempuri.org">
  <q:bodyB xmlns:q="http://tempuri.org">test</q:bodyB>
</replyBodyB>
<?xml version="1.0" encoding="IBM437"?>
<replyDefault xmlns="http://tempuri.org">
   <q:bodyX xmlns:q="http://tempuri.org">test</q:bodyX>
</replyDefault>

设置、生成和运行示例

  1. 确保已为 Windows Communication Foundation 示例 执行One-Time 安装过程。

  2. 要生成解决方案,请按照生成 Windows Communication Foundation 示例中的说明进行操作。

  3. 若要在单台计算机或跨计算机配置中运行示例,请按照 运行 Windows Communication Foundation 示例中的说明进行操作。