从SOAP请求中移除命名空间

10

我导入了一个WSDL并使用它来发送SOAP请求。它长这样:

<?xml version="1.0"?>
<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">
    <SOAP-ENV:Body>
        <Calculate xmlns="urn:xx.WSDL.xxxxxWebService">
            <ContractdocumentIn>
                <AL>
                ...More XML...
问题在于Calculate元素中的xmlns="urn:xx.WSDL.xxxxxWebService"部分。Web服务无法接受它。Web服务不喜欢这样的命名空间...
使用SoapUI,我发现这个请求完全正常:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:col="http://example.com.service.xxx/">
    <SOAP-ENV:Body>
        <col:Calculate>
            <ContractdocumentIn>
                <AL>
                    ...More XML...

那么,如何将请求从第一种格式更改为第二种格式?(不能使用不正当手段!)
(如果重新导入可以得到正确的请求格式,则不是问题。)




再次强调:禁止使用不正当手段,例如篡改请求流来修改它!


虽然我还没有完全测试过,但似乎C#/VS2010和Delphi 2010也无法使用我正在尝试调用的Web服务。这个Web服务似乎是用Java编写的。SoapUI恰好是用Java编写的,因此我们有一个Java客户端与一个Java服务进行通信,看起来工作得很好。但其他任何客户端呢?
无论如何,现在是时候添加两个标签了:"Java",因为它是一个Java服务,以及"vs2010",因为.NET也不喜欢这个服务。
我本来想在.NET中编写一个包装器来调用这个服务,希望能够工作......但事实并非如此。所以这是一个非常严重的缺陷,可能是Java的缺陷......


祝你好运。我不得不采取卑劣手段。 - Chris Thornton
是的,我知道。我可能可以使用一些不正当手段来解决它,但管理层不同意这样做。所以代码需要保持干净。 - Wim ten Brink
2
SOAP代码提供钩子来检查正在发送的XML并在需要时进行修改。它明确支持手动操作,而且代码会保留不受后期操作影响。虽然可能没有自动处理那么方便,但没有人应该担心它。 - mj2008
我知道。甚至不会太难。捕获BeforeExecute事件,读取XML,使用样式表进行转换,然后使用新的XML重新创建流。这就是我向管理层建议的。他们认为这太复杂了,现在我需要找到其他解决方案。如果没有其他解决方案,他们将不得不接受这一点... - Wim ten Brink
“简单”比“复杂”更加简洁。我使用StringReplace函数来去除内联命名空间,并在需要时注入“ns:”前缀。在我看来,如果Delphi能够创建与SoapUI相同的XML,以便轻松验证“我们正在正确地执行”,那将会更好。否则,当责任指向我们时,似乎总是有更多的Java和.Net开发人员,因此他们的方法是“正确”的,而Delphi方面必须修复他们的东西使其“看起来正确”。 - Chris Thornton
3
如果没有出现好的答案,我将出资奖励。 - Chris Thornton
2个回答

14
如果服务需要:
  <col:Calculate>
     <ContractdocumentIn>
         <AL>

而 Delphi SOAP 正在发送...

    <Calculate xmlns="urn:xx.WSDL.xxxxxWebService">
        <ContractdocumentIn>
            <AL>

...问题在于ContractdocumentIn是一个未限定的元素,而(直到Delphi XE)Delphi SOAP不支持作为操作顶级元素的未限定元素。顶级元素是函数的参数,没有地方存储底层元素必须是未限定的事实;对于映射到属性的元素,我们使用属性的索引来存储IS_UNQL标志。

顺便说一句,不必使用前缀。服务也将(应该)接受以下内容:

    <Calculate xmlns="urn:xx.WSDL.xxxxxWebService">
        <ContractdocumentIn xmlns="">
            <AL>

后者更冗长,但与前缀情况等效。
在Delphi XE中,导入程序会存储一个特定参数映射到未限定元素的事实,运行时会根据此信息进行操作。最近在新闻组中讨论时,我基于XE实现为D2010和D2007发布了补丁。

https://forums.embarcadero.com/thread.jspa?threadID=43057

如果有人需要访问它们(它们在附件区域中,但可能已经滚动出去了),请给我发送电子邮件,我会使它们可用。[bbabet at embarcadero dot com]
干杯,
布鲁诺

1
很好的解释!谢谢。 我正在使用带有SOAP库的D2005和D2007补丁。您认为您对D2007补丁的修复是否适用于我? - Chris Thornton
4
好的,我明白了。这句话的意思是“是的,应该这样做。我发布的补丁在这里:https://forums.embarcadero.com/thread.jspa?messageID=290788”。 - BruneauB
如果您遇到问题,请指向(或通过电子邮件发送)WSDL,我会进行调查。干杯! - BruneauB
非常好的解释。实际上,我调用的服务更喜欢像这样为'col:'添加一个额外的命名空间:<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:col="http://example.com.service/"><SOAP-ENV:Body><col:Calculate><ContractdocumentIn>但幸运的是,它也接受<Calculate xmlns="http://colan.ogconnect.service.wzp/"><ContractdocumentIn xmlns=""> - Wim ten Brink
这种方法看起来非常不错,我们现在正在进行实验,以查看其对一些现有服务的影响。例如,“我们能够摆脱多少丑陋的StringReplace操作?”我们将在下周报告我们的结论。 - Chris Thornton
接受此答案是因为它解释了为什么会出现这个问题。补丁也非常有用!此外,它证实了我找到的解决方案。 - Wim ten Brink

8

哇塞!我喝了很多咖啡,睡眠也不足,但我终于解决了我的问题!而且方法还相当简单...
首先按预期导入WSDL。这将生成几个TRemotable类。然后,对于每个需要不同命名空间的TRemotable,我覆盖了ObjectToSOAP()方法!(并在WSDL源代码中包含XMLIntf。)在我的情况下,对于几个远程类型的代码如下:

function AL2.ObjectToSOAP( RootNode, ParentNode: IXMLNode; const ObjConverter: IObjConverter; const NodeName, NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions; out RefID: InvString ): IXMLNode;
begin
  Result := inherited ObjectToSOAP( RootNode, ParentNode, ObjConverter, NodeName, '', '', ObjConvOpts, RefID );
end;

在Delphi XE中可以正常工作。在Delphi 2007中,我需要使用XMLIntf和XMLDoc单元以及以下输入类型的代码:
function ContractdocumentInType.ObjectToSOAP(RootNode, ParentNode: IXMLNode; const ObjConverter: IObjConverter; const Name, URI: InvString; ObjConvOpts: TObjectConvertOptions; out RefID: InvString): IXMLNode;

  procedure AlterChildren(Child: IXMLNode);
  var
    I: Integer;
  begin
    if (Child.NodeType = ntElement) then Child.SetAttributeNS('xmlns', '', '');
    for I := 0 to Pred(Child.ChildNodes.Count) do
      AlterChildren(Child.ChildNodes[I]);
  end;

begin
  Result := inherited ObjectToSOAP(RootNode, ParentNode, ObjConverter, Name, '', ObjConvOpts, RefID);
  AlterChildren(Result);
end;

在我看来,这是一种黑客行为。但这不是非常恶劣的黑客行为。它是一种试验,捕获SOAP请求和响应以检查其内容并查看是否使用适当的命名空间。不幸的是,Delphi 2007在这方面做得远不如Delphi XE。
尽管如此,我仍然保持着这个问题的开放,以寻求更好的解决方案...
另外,为了将col:添加到输出中,我还必须更改WSDL中的这一行:RemClassRegistry.RegisterXSClass(Calculate,'http://colan.ogconnect.service.wzp/','Calculate'); 更改为: RemClassRegistry.RegisterXSClass(Calculate,'http://colan.ogconnect.service.wzp/','cal:Calculate');。结果就变成了<cal:Calculate xmlns:cal="http://example.webservice/">。不过,还需要做一件事情:将xmlns:cal移动到xml头部。但现在它对我来说已经足够好用了。
另一个注意点:对于WSDL,我使用了以下设置:“一个输出参数是返回值”,“展开文字参数”,“生成解构器”,“警告注释”,"发出文字类型",“将字符串映射为widestring”。其他选项包括:“生成有关类型和接口的详细信息”,“忽略具有HTTP绑定的端口类型”,“验证枚举成员”、“导入故障类型”、“导入标头类型”、“处理包含和导入的模式”、“将类别名生成为类别型”,“允许输出参数”和“处理可空和可选元素”。 "发出文字类型"是实用的,因为它在我调用的单个方法周围生成一个类。不幸的是,即使这个类可以帮助您在信封中覆盖 ObjectToSOAP() 方法以修改SOAP请求的上层,它也没有太大帮助。
创建信封本身就在SOAPEnv单元中,并且在OPToSOAPDomConv单元中使用。不幸的是,我还没有找到一个轻松的方法来访问信封本身以修改标题以添加这个附加命名空间。不过,我可以用自己的版本重写TSOAPDomConv类,以添加额外的命名空间。但是,现在代码对我来说已经足够好了,就像我父亲告诉我的一样:从不更改不需要修复的东西。

看起来很有前途 - 我要试一试。这可能会在即将到来的大项目中节省很多时间/痛苦,所以我很乐意为讨论贡献赏金点数。 - Chris Thornton
它真的给你输出了这样的结果吗:col:Calculate - Chris Thornton
col:calculate 是通过第二个技巧完成的。请查看详细答案。 - Wim ten Brink
1
我将创建一个新类:TRemotableFix,其中包含ObjectToSOAP重写。然后,我可以简单地修改生成的代理,将所有/大多数TRemotable更改为TRemotableFix,添加我的新中间类的Uses,然后对RegisterXSClass进行编辑。我需要一个“食谱”解决方案,可以快速应用于任何代理类,因为我需要能够重新使用WSDL并且不会破坏一切。即代理类的更改需要简短而简洁,并且可以通过svn历史轻松发现。 - Chris Thornton
我仍然希望有更好的解决方案...但是,我在 Delphi 2007 代码中搜索得越多,我就越认为这只是最佳解决方案。ObjectToSOAP() 似乎是专门为此目的而虚拟化的... - Wim ten Brink
显示剩余2条评论

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