Delphi SOAP信封和WCF

12

我正在开发一个提供SOAP接口的系统。其中一个将使用该接口的系统是用Delphi 7编码的。Web服务是使用WCF、基本的HTTP绑定和SOAP 1.1开发的。

如果我使用SOAP UI(JAVA),服务正常工作。但是Delphi似乎在这里做了一些特殊处理;)

这是SOAP UI中消息的样子:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.xxx.de/xxx">
   <soapenv:Header/>
   <soapenv:Body>
      <ser:GetCustomer>
         <!--Optional:-->
         <ser:GetCustomerRequest> <!-- this is a data contract -->
            <ser:Id>?</ser:Id>
         </ser:GetCustomerRequest>
      </ser:GetCustomer>
   </soapenv:Body>
</soapenv:Envelope>

我不是Delphi开发人员,但是我开发了一个简单的测试客户端来查看问题所在。这就是Delphi发送的SOAP信封。

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:NS2="http://services.xxx.de/xxx">
    <NS1:GetCustomer xmlns:NS1="http://services.xxx.de/xxx">
      <GetCustomerRequest href="#1"/>
    </NS1:GetCustomer>
    <NS2:GetCustomerRequest id="1" xsi:type="NS2:GetCustomerRequest">
      <Id xsi:type="xsd:int">253</Id>
    </NS2:GetCustomerRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

在 WCF 中出现了德语错误... ;)

Endelement "Body" 在命名空间 "http://schemas.xmlsoap.org/soap/envelope/" 中未找到,反而找到命名空间为 "http://services.xxx.de/xxx" 的元素 "NS2:GetCustomerRequest"。第一行,第599个字符。

意思是:

预期的是 Body 元素,但实际上找到的是 NS2:GetCustomerRequest 元素。

现在我的问题是:我是否能改变 Delphi 创建邮件的方式?或者有没有其他方法让 WCF 能够处理这种消息格式?非常感谢您的帮助!


1
问题不在于NS1 NS2部分,而是Delphi生成的XML结构不正确。看起来Web服务期望GetCustomerRequest嵌套在GetCustomer内部,但是Delphi客户端没有嵌套GetCustomerRequest元素。我不知道如何修复它。你是使用WCF服务的WSDL生成Delphi客户端吗? - Erv Walter
3个回答

17
与一些人在这里暗示的相反,Delphi并没有发送无效的SOAP请求,它只是发送了RPC/Encoded格式的SOAP请求。你可以很容易地通过所有xsi:type属性来识别RPC/Encoded格式。虽然RPC/Encoded不符合WS-I标准,但仍然是有效的SOAP请求。
WCF默认使用Document/Literal/Wrapped SOAP格式,在服务器端Delphi 7无法处理,你需要在客户端进行一些调整。
最简单的解决方案是告诉Delphi使用Document/Literal格式。您可以通过在THttpRio.Converter.Options中启用soLiteralParams来实现此目的。这将告诉Delphi不要像您看到的那样“展开”参数。文档方面通常是Delphi WSDL导入程序可以自行处理的,因此您不必担心。
另一个解决方案是告诉WCF服务使用RPC/Encoded格式,您可以通过向服务添加以下属性来实现:
[ServiceContract]
[XmlSerializerFormat(Style = OperationFormatStyle.Rpc,
    Use = OperationFormatUse.Encoded)]
public interface IMyService
{
    // etc.
}

第二种方法并不被推荐,因为正如我之前提到的那样,RPC/Encoded不符合WS-I标准,但大多数SOAP工具包仍然可以识别它,所以我将它列在这里作为一种可能性。


4

我刚刚完成了其中一项任务,最终得到了一系列的字符串替换调用,以修改我的XML输出,去除内联命名空间,并使其看起来像SoapUI的格式。是的,这需要大量手动操作。

例如:

在创建RIO之后,调用您自己的BeforeExecute过程:

...
 EEUPSERTRIO.OnBeforeExecute := self.RIO_BeforeExecute;
...

procedure TMyWrapper.RIO_BeforeExecute(const MethodName: string; var SOAPRequest: WideString);
{
Since Delphi isn't very good at SOAP, we need to fix the request so that the namespaces are correct.
Basically, you take what Delphi gives you and try it in SoapUI.
If yours doesn't work and SoapUI's version does, make yours look like theirs.
}

现在请去掉内联命名空间:
SOAPRequest := StringReplace(SOAPRequest,' xmlns:NS1="http://services.xxx.de/xxx"','',[rfReplaceAll,rfIgnoreCase]);

有很多这样的内容。

然后,您将使用包含您想要的命名空间的 soap 标头替换它。

SOAPRequest := StringReplace(SOAPRequest,'xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"','xmlns:ns1="http://services.xyzcorp.com/xyz/EnterpriseEmployeeService_1_0" '+'xmlns:ns1="http://schemas.xyzcorp.com/TLOIntegration_HRO_Preview/TLOIntegration_1_0" ',[]);

然后您可以重新注入好的代码:
  ReplaceTag(SOAPRequest,'<metaData>','ns1:');
  ReplaceTag(SOAPRequest,'<trackingId>','ns1:');
  ReplaceTag(SOAPRequest,'<srcSystem>','ns1:');

最后,您可以通过使用SoapUI重新消耗WSDL并将其托管为mockservice来轻松捕获Delphi输出。然后将您的应用程序指向它作为端点,它将捕获输出。
或者,您可以使用Fiddler作为代理来捕获请求。

FYI - 我们使用的是带有D2007修复程序的D2005。 - Chris Thornton
2
添加到 Delphi 挑剔列表中... http://stackoverflow.com/questions/2112729/biggest-delphi-nitpicks/2473684#2473684 - Chris Thornton
感谢您的决定。这是真正有帮助的一个。 - Andrew Kalashnikov

-1

Delphi和Java框架使用不同的命名空间。使其兼容的一种方法是拦截原始XML并将所有“NS2”更改为反序列化程序所期望的内容。

干杯


对于符合标准的SOAP解析器来说,命名空间前缀并不重要。 - mjn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接