【问题标题】:WCF Client: Forcing Global NamespacesWCF 客户端:强制全局命名空间
【发布时间】:2016-03-26 02:06:33
【问题描述】:

我正在与一个似乎不处理默认名称空间的 SOAP 服务进行交互,但它可以很好地处理在 SOAP 信封级别声明的全局名称空间和名称空间前缀。

问题在于 WCF 不会在根目录创建这些全局名称空间,而是使用显式的无前缀默认名称空间,服务显然会阻塞这些名称空间。现在我知道这并不是 WCF 的错 - 我相信 WCF 生成的消息是有效的 XML,但服务仍然会阻塞它。

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

<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 中手动)确实有效:

<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命名空间是在文档顶部全局声明的,而第二个文档中没有局部命名空间声明。

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

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

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

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

有什么想法可以强制 WCF 在不手动重写消息的情况下使用显式命名空间前缀?

【问题讨论】:

  • 我最近遇到了同样的情况,试图将现有的命名空间与新的命名空间合并。我无法给出明确的答案。我确实发现,在我删除了合并、重建、退出 VS,然后集成现有代码的尝试之后,一切都很好。蹩脚的答案,我同意。我得出的结论是它是 VS 中的错误,但过于复杂而无法尝试重现场景。祝你好运,我等待更好的解决方案/回答您的情况。
  • 您尝试过this blog postthis answer中的建议吗?
  • 谢谢理查德。这篇博文很有帮助,为我指明了正确的方向。

标签: c# wcf soap


【解决方案1】:

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

为此需要创建 3 个类:

  • 处理实际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 将消息格式化程序附加到实际操作。

为了解决这个问题,我将属性添加到服务接口上的客户端方法中 - 在本例中,是在 Reference.cs 中生成的 WCF 客户端中。

    // 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 生成的消息现在看起来与预期的一样,命名空间都在文档顶部定义:

<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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2015-08-16
  • 2012-02-29
  • 1970-01-01
  • 2019-12-20
  • 2012-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多