如何修改WCF以处理不同(非SOAP)格式的消息?

时间:2010-12-14 09:31:43

标签: xml wcf wcf-binding ebxml

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

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

我已经能够找到很多关于每个实现的详细文档,但很少关于如何将它们全部放在一起。在Peiris和Mulder的“Pro WCF”中,我所拥有的书籍似乎也同样关注这些主题。

我的目标是以下内容。

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

<?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;
            };
    }
}

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:12)

我执行Tim回答的详细信息

我需要为我目前正在工作的客户写这篇文章,所以我想我也可以在这里发布。我希望它对某人有帮助。我已经创建了一个示例客户端和服务,我曾经尝试过这些想法。我把它清理干净并添加到github。你可以download it here

需要实现以下内容以允许以我需要的方式使用WCF:

  1. WCF不期望SOAP消息
  2. 完全根据需要格式化请求和响应消息的能力
  3. 要考虑处理的所有邮件
  4. 传入的消息将路由到正确的操作以处理它们
  5. <强> 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的实例。以下是从我的端点行为配置中说明这一点的片段。

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

    <强> 4。选择正确的操作

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

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

    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中选择了这个。

    有用的文档链接

    到目前为止,我发现的最佳参考资料是服务站MSDN杂志文章(谷歌“网站:msdn.microsoft.com服务站WCF”)。

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

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

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

答案 1 :(得分:4)

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

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

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

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

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

答案 2 :(得分:1)

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

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