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

Mik*_*keD 11 xml wcf wcf-binding ebxml

我正在与WCF合作,与第三方公司交换消息.需要在与ebXML规范匹配的信封中发送和接收消息.理想情况下,我希望尽可能多地使用WCF堆栈,并避免使用一种方法来处理它们,因为在这种情况下,这意味着要再次编写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>
Run Code Online (Sandbox Code Playgroud)

响应将是:

<?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>
Run Code Online (Sandbox Code Playgroud)

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

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

public class MyEndpoint : IMyEndpoint
{
    public AcknowledgementResponse DoSomething(AnObject value)
    {
        return new AcknowledgementResponse
            {
                Ok = True;
            };
    }
}
Run Code Online (Sandbox Code Playgroud)

任何帮助将非常感激.

Mik*_*keD 12

我实施Tim的答案的详细信息

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

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

  1. WCF不期望SOAP消息
  2. 能够根据需要格式化请求和响应消息
  3. 要考虑处理的所有消息
  4. 传入的消息将路由到正确的操作以处理它们

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

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

  <customBinding>
    <binding name="poxMessageBinding">
      <textMessageEncoding messageVersion="None" />
      <httpTransport />
    </binding>
  </customBinding>
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

然后它只是使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

3.处理所有传入的消息

为了指示WCF允许处理所有传入消息,我需要将endpointDispatcher上的ContractFilter属性设置为MatchAllMessageFilter的实例.这是一个片段,通过我的端点行为配置说明了这一点.

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

将它全部包装起来

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

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

有用的文档链接

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

深度WCF绑定 - 有关配置绑定的非常有用的信息

使用自定义行为扩展WCF - 我已经找到的有关调度程序集成点的最佳信息源,它们包含一些非常有用的图表,用于说明所有集成点以及它们按处理顺序出现的位置.

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


Tim*_*rts 5

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

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

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

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

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