WCF 客户端:强制全局命名空间 [英] WCF Client: Forcing Global Namespaces

查看:23
本文介绍了WCF 客户端:强制全局命名空间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究与 SOAP 服务的接口,该服务似乎不处理默认名称空间,但可以很好地处理全局名称空间和在 SOAP 信封级别声明的名称空间前缀.

I'm working on interfacing with a SOAP service that appears to not deal with default namespaces, but works fine with global namespaces and namespace prefixes declared at the SOAP envelope level.

问题是 WCF 不会在根创建这些全局命名空间,而是使用显式的不带前缀的默认命名空间,该服务显然正在阻塞这些命名空间.现在我知道这并不是 WCF 的错 - 我相信 WCF 生成的消息是有效的 XML,但服务仍然在它上面窒息.

The problem is that WCF doesn't create these global namespaces at the root, but rather uses explicit unprefixed default namespaces which the service apparently is choking on. Now I know that this not really WCF's fault - I believe the WCF generated messages are valid XML, but the service chokes on it nonetheless.

使用 WCF 生成的输出如下所示:

Using WCF the output generated looks like this:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-
          ...
    </h:Security>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <cancelShipmentRequest xmlns="http://www.royalmailgroup.com/api/ship/V2">
      <integrationHeader>
        <dateTime xmlns="http://www.royalmailgroup.com/integration/core/V1">2016-03-26T01:44:37.0493801Z</dateTime>
        <version xmlns="http://www.royalmailgroup.com/integration/core/V1">2</version>
        <identification xmlns="http://www.royalmailgroup.com/integration/core/V1">
          <applicationId>RMG-API-G-01</applicationId>
          <transactionId>ozhckwej6sxg</transactionId>
        </identification>
      </integrationHeader>
      <cancelShipments>
        <shipmentNumber>TTT001908905GB</shipmentNumber>
      </cancelShipments>
    </cancelShipmentRequest>
  </s:Body>
</s:Envelope>

这不起作用.

使用以下 SOAP 信封(在 SoapUI 中手动)确实有效:

Using the following SOAP envelope (manually in SoapUI) does work however:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
  xmlns:v2="http://www.royalmailgroup.com/api/ship/V2"
  xmlns:v1="http://www.royalmailgroup.com/integration/core/V1">
  <soapenv:Header>
    <h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
       ...
    </h:Security>
  </soapenv:Header>
  <soapenv:Body>
    <v2:cancelShipmentRequest>
      <v2:integrationHeader>
        <v1:dateTime>2016-03-02T14:55:00Z</v1:dateTime>
        <v1:version>2</v1:version>
        <v1:identification>
          <v1:applicationId>RMG-API-G-01</v1:applicationId>
          <v1:transactionId>wftdaife96gv</v1:transactionId>
        </v1:identification>
      </v2:integrationHeader>
      <v2:cancelShipments>
        <v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber>
      </v2:cancelShipments>
    </v2:cancelShipmentRequest>
  </soapenv:Body>
</soapenv:Envelope>

两者的区别在于 v1 和 v2 命名空间是在文档顶部全局声明的,而在第二个文档中没有本地命名空间声明.

The difference between the two is that the v1 and v2 namespaces are declared globally at the top of the document and there are no local namespace declarations in the second document.

也许我遗漏了一些东西,但对我来说,WCF 生成的 XML 看起来是有效的,并且在命名空间方面表示相同的文档状态.

Maybe I'm missing something but to me the WCF generated XML looks to be valid and represent the same document state in terms of the namespacing.

我能说的唯一区别是命名空间的声明方式.尽管 WCF 版本似乎有效并产生相同的命名空间,但该服务仍抱怨命名空间引用无效.

The only difference I can tell is that the way the namespaces are declared. And although the WCF version seems to be valid and producing the same namespacing, the service complains about invalid namespace references.

架构验证失败:消息架构验证失败:架构有效性错误:元素xmlns":不需要此元素.预期是 ( {http://www.royalmailgroup.com/api/ship/V2}integrationHeader).

Failed Schema Validation: Message failed schema validation: Schemas validity error : Element 'xmlns': This element is not expected. Expected is ( {http://www.royalmailgroup.com/api/ship/V2}integrationHeader ).

问题是,强制 WCF 在顶部而不是内联添加命名空间引用的最佳方法是什么?到目前为止,我发现的唯一方法是使用消息检查器并显式重写消息,但如果我完成所有这些,我还不如手动创建消息.

The question is, what's the best way to force the WCF to add the namespace references at the top instead of inline? The only way I've found so far is to use a message inspector and explicitly rewrite the message, but if I go through all that I might as well just manually create the messages.

我可以尝试强制 WCF 使用显式命名空间前缀而不手动重写消息的任何想法吗?

Any ideas what I can try to force WCF to use explicit namespace prefixes without manually rewriting the messages?

推荐答案

所以这个问题的答案是创建一个自定义的 IClientMessageFormatterMessage 然后覆盖 >Message.OnWriteStartEnvelope() 显式写出 Soap 文档根目录下的所有命名空间.渲染的文档然后重用这些命名空间,而不是在子元素上显式分配命名空间.

So the answer to this problem was to create a custom IClientMessageFormatter and Message then overriding the Message.OnWriteStartEnvelope() to explicitly write out all the namespaces at the Soap document root. The rendered document, then reuses these namespaces instead of explicitly assigning namespaces on child elements.

要为此创建 3 个类:

There are 3 classes to be created for this to work:

  • 处理实际OnWriteStartEnvelope()
  • 的消息实现
  • 连接到 WCF 的 IClientMessageFormatter
  • FormatMessageAttribute 附加到客户端的每个方法

这是所有三个的代码:

public class RoyalMailCustomMessage : Message
{
    private readonly Message message;

    public RoyalMailCustomMessage(Message message)
    {
        this.message = message;
    }
    public override MessageHeaders Headers
    {
        get { return this.message.Headers; }
    }
    public override MessageProperties Properties
    {
        get { return this.message.Properties; }
    }
    public override MessageVersion Version
    {
        get { return this.message.Version; }
    }

    protected override void OnWriteStartBody(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/");
    }
    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        this.message.WriteBodyContents(writer);
    }
    protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer)
    {
        writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
        writer.WriteAttributeString("xmlns", "oas", null, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        writer.WriteAttributeString("xmlns", "v2", null, "http://www.royalmailgroup.com/api/ship/V2");
        writer.WriteAttributeString("xmlns", "v1", null, "http://www.royalmailgroup.com/integration/core/V1");
        writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
        writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");            
    }
}

public class RoyalMailMessageFormatter : IClientMessageFormatter
{
    private readonly IClientMessageFormatter formatter;

    public RoyalMailMessageFormatter(IClientMessageFormatter formatter)
    {
        this.formatter = formatter;
    }

    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
    {
        var message = this.formatter.SerializeRequest(messageVersion, parameters);
        return new RoyalMailCustomMessage(message);
    }

    public object DeserializeReply(Message message, object[] parameters)
    {
        return this.formatter.DeserializeReply(message, parameters);
    }
}


[AttributeUsage(AttributeTargets.Method)]
public class RoyalMailFormatMessageAttribute : Attribute, IOperationBehavior
{
    public void AddBindingParameters(OperationDescription operationDescription,
        BindingParameterCollection bindingParameters)
    { }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {
        var serializerBehavior = operationDescription.Behaviors.Find<XmlSerializerOperationBehavior>();

        if (clientOperation.Formatter == null)
            ((IOperationBehavior)serializerBehavior).ApplyClientBehavior(operationDescription, clientOperation);

        IClientMessageFormatter innerClientFormatter = clientOperation.Formatter;
        clientOperation.Formatter = new RoyalMailMessageFormatter(innerClientFormatter);
    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    { }

    public void Validate(OperationDescription operationDescription) { }
}

其中大部分是仪式和样板代码.关键的代码片段是 OnWriteStartEnvelope,其中连接了实际的命名空间,SerializeRequest,其中格式化程序连接到 WCF 管道中,以及 ApplyClientBehavior 中的消息formatter 附加到实际操作中.

Most of this is ceremony and boilerplate code. The key code pieces are OnWriteStartEnvelope where the actual namespaces are hooked up, SerializeRequest where the formatter is hooked into the WCF pipeline and ApplyClientBehavior where the message formatter is attached to the actual operation.

为了实现这一点,我将属性添加到服务接口上的客户端方法中 - 在本例中是在 Reference.cs 中生成的 WCF 客户端中.

To hook this up, I added the attribute to client method on the service interface - in this case in my generated WCF client in Reference.cs.

    // CODEGEN: Generating message contract since the operation cancelShipment is neither RPC nor document wrapped.
    [System.ServiceModel.OperationContractAttribute(Action="cancelShipment", ReplyAction="*")]
    [System.ServiceModel.FaultContractAttribute(typeof(MarvelPress.Workflow.Business.RoyalShippingApi.exceptionDetails), Action="cancelShipment", Name="exceptionDetails")]
    [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(contactMechanism))]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(baseRequest))]
    [RoyalMailFormatMessage()]
    MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentResponse1 cancelShipment(MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentRequest1 request);

从 WCF 生成的消息现在看起来像预期的一样,命名空间全部定义在文档顶部:

Messages generated from WCF now look as expected with the namespaces all defined at the top of the document:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:v2="http://www.royalmailgroup.com/api/ship/V2" xmlns:v1="http://www.royalmailgroup.com/integration/core/V1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <h:Security>...</h:Security>
  </s:Header>
  <soapenv:Body>
    <v2:cancelShipmentRequest>
      <v2:integrationHeader>
        <v1:dateTime>2016-04-02T01:04:50.4122473Z</v1:dateTime>
        <v1:version>2</v1:version>
        <v1:identification>
          <v1:applicationId>RMG-API-G-01</v1:applicationId>
          <v1:transactionId>fshrxevdnc7n</v1:transactionId>
        </v1:identification>
      </v2:integrationHeader>
      <v2:cancelShipments>
        <v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber>
      </v2:cancelShipments>
    </v2:cancelShipmentRequest>
  </soapenv:Body>
</soapenv:Envelope>    

有关更多信息和通用命名空间添加格式化程序,请查看我的相关博客文章:http://weblog.west-wind.com/posts/2016/Apr/02/Custom-Message-Formatting-in-WCF-to-add-all-Namespaces-to-the-SOAP-Envelope

For more info and a generic namespace adding formatter check out my related blog post here: http://weblog.west-wind.com/posts/2016/Apr/02/Custom-Message-Formatting-in-WCF-to-add-all-Namespaces-to-the-SOAP-Envelope

这篇关于WCF 客户端:强制全局命名空间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆