【问题标题】:How do I modify WCF to process messages in a different (non SOAP) format?如何修改 WCF 以处理不同(非 SOAP)格式的消息?
【发布时间】:2011-05-25 04:07:54
【问题描述】:

我正在与 WCF 合作,与第三方公司交换消息。消息需要在与ebXML specification 匹配的信封中发送和接收。理想情况下,我希望尽可能多地使用 WCF 堆栈并避免使用 one method to process them all 方法,因为在这种情况下,这意味着要再次编写 WCF 的大部分基础架构。

从我最初的研究中可以看出,这需要我编写自己的自定义绑定,但我很难在 MSDN 的文档中找到清晰的内容。

我已经能够找到很多关于每个单独实现的详细文档,但关于如何将它们端到端组合在一起的信息却很少。看来我的书对这些主题也同样轻描淡写,在 Peiris 和 Mulder 的“Pro WCF”中没有提到这一点。

我的目标是如下。

发送和接收的消息必须采用如下格式,其中第一个元素的名称是要执行的操作的名称,子元素是请求消息的负载,格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<op:DoSomething xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com">
    <op:AnObject>
        <payload:ImportantValue>42</payload:ImportantValue>
    </op:AnObject>
</op:DoSomething>

响应将是:

<?xml version="1.0" encoding="UTF-8"?>
<op:AcknowledgementResponse xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com">
    <op:ResponseObject>
        <payload:Ok>True</payload:Ok>
    </op:ResponseObject>
</op:AcknowledgementResponse>

由于消息都是由 XML 模式描述的,因此我使用 XSD.exe 将它们转换为强类型对象。有关架构,请参阅 https://gist.github.com/740303。请注意,这些是示例模式。在不违反客户保密协议的情况下,我无法发布真实的架构(您也不会想要我,因为它们很大)。

我现在希望能够编写如下服务实现:

public class MyEndpoint : IMyEndpoint
{
    public AcknowledgementResponse DoSomething(AnObject value)
    {
        return new AcknowledgementResponse
            {
                Ok = True;
            };
    }
}

任何帮助将不胜感激。

【问题讨论】:

  • 您能否在 gist.github.com 或类似服务上提供完整的请求和响应消息(可能带有 xsd)?
  • @larsw 我无法为我的客户提供架构,但我已经为我的示例消息创建了架构,并且我已经用指向它们的链接更新了我的问题。对我来说,关键是对一般方法的帮助,我认为我的示例消息和模式应该足够了。

标签: xml wcf wcf-binding ebxml


【解决方案1】:

我对 Tim 的回答的实施细节

我需要为我目前工作的客户写这篇文章,所以我想我也不妨把它贴在这里。我希望它可以帮助某人。我创建了一个示例客户端和服务,用于尝试其中的一些想法。我已经清理了它并将其添加到 github。你可以download it here

为了让 WCF 以我要求的方式使用,需要实现以下几点:

  1. WCF 不期待 SOAP 消息
  2. 能够完全按照要求格式化请求和响应消息
  3. 考虑处理的所有消息
  4. 传入的消息被路由到正确的操作来处理它们

1.将 WCF 配置为不期望 SOAP 消息

第一步是通过 TextMessageEncoder 获取传入消息。这是通过在 textMessageEncoding 元素上使用带有 MessageVersion.None 设置的自定义绑定来实现的。

  <customBinding>
    <binding name="poxMessageBinding">
      <textMessageEncoding messageVersion="None" />
      <httpTransport />
    </binding>
  </customBinding>

2。正确格式化消息

消息格式化程序是必需的,因为传入消息拒绝被现有的 XML 格式化程序反序列化,而无需在消息合同上添加其他属性。这通常不是问题,但是通过 XSD.exe 运行我的客户端 ebXML 模式会生成一个 33000 行的 cs 文件,我不想以任何方式修改它。此外,人们将来会忘记重新添加属性,因此希望这也使维护更容易。

自定义格式化程序期望将传入消息转换为第一个参数的类型,并将返回类型转换为响应消息。它检查实现方法以确定构造函数中第一个参数的类型和返回值。

public SimpleXmlFormatter(OperationDescription operationDescription)
{
    // Get the request message type
    var parameters = operationDescription.SyncMethod.GetParameters();
    if (parameters.Length != 1)
        throw new InvalidDataContractException(
"The SimpleXmlFormatter will only work with a single parameter for an operation which is the type of the incoming message contract.");
    _requestMessageType = parameters[0].ParameterType;

    // Get the response message type
    _responseMessageType = operationDescription.SyncMethod.ReturnType;
}

然后它简单地使用 XmlSerializer 序列化和反序列化数据。对我来说,其中一个有趣的部分是使用自定义 BodyWriter 将对象序列化为 Message 对象。下面是服务序列化程序的部分实现。客户端实现则相反。

public void DeserializeRequest(Message message, object[] parameters)
{
    var serializer = new XmlSerializer(_requestMessageType);
    parameters[0] = serializer.Deserialize(message.GetReaderAtBodyContents());
}

public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
    return Message.CreateMessage(MessageVersion.None, _responseMessageType.Name,
                                 new SerializingBodyWriter(_responseMessageType, result));
}

private class SerializingBodyWriter : BodyWriter
{
    private readonly Type _typeToSerialize;
    private readonly object _objectToEncode;

    public SerializingBodyWriter(Type typeToSerialize, object objectToEncode) : base(false)
    {
        _typeToSerialize = typeToSerialize;
        _objectToEncode = objectToEncode;
    }

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        writer.WriteStartDocument();
        var serializer = new XmlSerializer(_typeToSerialize);
        serializer.Serialize(writer, _objectToEncode);
        writer.WriteEndDocument();
    }
}

3.处理所有传入消息

为了指示 WCF 允许处理所有传入消息,我需要将 endpointDispatcher 上的 ContractFilter 属性设置为 MatchAllMessageFilter 的实例。这是一个从我的端点行为配置中说明这一点的 sn-p。

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
    endpointDispatcher.ContractFilter = new MatchAllMessageFilter();
    // Do more config ...
}

4.选择正确的操作

这是通过创建一个实现 IDispatchOperationSelector 的类来实现的。在 SelectOperation 方法中,我检查传入的消息。在这里,我正在寻找两件事: 1. 根元素的命名空间与服务合约的命名空间相同的完整性检查 2.根元素的名字(注意使用LocalName去掉任何命名空间前缀)

根元素的名称是返回值,它将映射到合约上具有匹配名称或操作属性具有匹配值的任何操作。

public string SelectOperation(ref Message message)
{
    var messageBuffer = message.CreateBufferedCopy(16384);

    // Determine the name of the root node of the message
    using (var copyMessage = messageBuffer.CreateMessage())
    using (var reader = copyMessage.GetReaderAtBodyContents())
    {
        // Move to the first element
        reader.MoveToContent();

        if (reader.NamespaceURI != _namespace)
            throw new InvalidOperationException(
"The namespace of the incoming message does not match the namespace of the endpoint contract.");

        // The root element name is the operation name
        var action = reader.LocalName;

        // Reset the message for subsequent processing
        message = messageBuffer.CreateMessage();

        // Return the name of the action to execute
        return action;
    }
}

总结一下

为了更容易部署,我创建了一个端点行为来处理消息格式化程序、合同过滤器和操作选择器的配置。我也可以创建一个绑定来包装自定义绑定配置,但我认为这部分不太难记住。

对我来说一个有趣的发现是端点行为可以为端点中的所有操作设置消息格式化程序以使用自定义消息格式化程序。这节省了单独配置这些的需要。我是从Microsoft samples 中选择的。

有用的文档链接

到目前为止,我找到的最佳参考资料是 Service Station MSDN 杂志文章(Google “site:msdn.microsoft.com service station WCF”)。

WCF Bindings in Depth - 关于配置绑定的非常有用的信息

Extending WCF with Custom Behaviours - 我发现的关于调度程序集成点的最佳信息来源,其中包含一些非常有用的图表,说明了所有集成点以及它们在处理顺序中出现的位置。

Microsoft WCF samples - 这里有很多其他地方没有很好地记录。我发现阅读其中一些非常有启发性的源代码。

【讨论】:

  • 我觉得不错!我猜您在实现自定义消息格式化程序时仍然必须选择适当的编码,并且我猜 HTTP 绑定默认情况下会给您一个 SOAP 绑定。消息格式化程序的实现也是我希望完成的(委托给 bog 标准 XML 序列化程序)。虽然 EndpointBehavior 的使用是我没有想到的,但我想它确实让事情变得更容易。我已经为您的答案 +1,因为您值得代表努力发布您的解决方案。我也会去 GitHub 看看。
【解决方案2】:

我认为您不需要对绑定做任何事情。我假设您仍然需要通过 HTTP 发送 ebXML 格式的消息?

@ladislav 的回答是一种方法,但我认为消息编码器的设计水平远低于您想要达到的水平。它们本质上是对进出底层流的消息进行编码的部分(即,消息如何在流中表示为字节)。

我认为您需要做的是实现custom Message Formatter。特别是,既然您说要将消息提交给第三方,那么我认为您需要实现的只是IClientMessageFormatter 接口。另一个接口(@98​​7654323@)用于服务器端。

您还需要实现适当的 ServiceBehavior 和 OperationBehavior 以将格式化程序安装到堆栈中,但用于此的代码将是最少的(大部分代码将用于实现上述接口)。

实施后,您可以使用“一种方法来处理所有这些”方法来测试和调试您的格式化程序。只需将收到的消息转储到控制台以供您查看,然后还发送回 ebXML 响应。您也可以使用相同的方法来构建您的单元测试。

【讨论】:

  • 对不起,我不小心写了,我只需要发送这些消息。我已经纠正了。我也需要接收这些消息。我需要确保接收到的消息由正确的服务方法处理,但这需要一个操作标头。似乎填充动作标题的唯一方法是在绑定中提供它,除非我错过了什么。
  • @MikeD 好的,所以您也将在服务器端工作。在这种情况下,您还需要实现 IDispatchMessageFormatter 接口,然后您还需要一种将传入消息与正确操作相关联的方法。我认为这可以使用 IDispatchOperationSelector 来完成,但目前我不确定如何将其引入堆栈(可能通过您将用于添加到格式化程序中的 Service/OperationBehavior)。
  • 我想你就在这里。我已经尝试过自定义 IDispatchOperationSelector,如果它与只匹配所有消息的合同过滤器配对,我不需要绑定扩展。我今天下午试试。
  • 谢谢蒂姆。你是对的,我可以使用自定义操作选择器和消息格式化程序来完成这一切。在自定义绑定扩展中添加操作标头会分散注意力,因为那时我不知道合同过滤器。我将针对我的问题发布一个示例项目,其中包含我的解决方案。
  • 我已经在 github 上发布了我对您的建议的实施作为对这个问题的另一个答案。我很想听听您的反馈。
【解决方案3】:

对于自定义消息格式,您需要自定义MessageEncoderMSDN 包含如何创建自定义编码器的示例。如果您使用Reflector,您会发现几个编码器实现,因此您可以学习如何编写它。

您还可以检查如果您尝试将 TextMessageEncoder 与 MessageVersion.None 一起使用会发生什么(我从未尝试过)。

【讨论】:

  • mmka 这是一个很好的建议。我一直在使用 TextMessageEncoder 和 MessageVersion.None 进行相同的研究。我现在需要的只是一种获取第一个元素名称的操作的方法...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多