12

我正在与 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>

回应是:

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

任何帮助将非常感激。

4

3 回答 3

12

我实施蒂姆回答的细节

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

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 删除任何命名空间前缀)

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

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

有用的文档链接

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

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

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

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

于 2010-12-16T15:12:13.627 回答
5

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

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

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

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

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

于 2010-12-15T09:49:18.830 回答
1

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

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

于 2010-12-14T12:11:12.347 回答