在 C# 中序列化 SOAP 参数
我正在尝试为供应商的 SOAP 服务编写 .Net 客户端,但无法将 SOAP 消息的参数序列化为服务可识别的形式。
使用 wsdl.exe 我生成了一个服务代理类,它本身工作得很好。然而,其中一条消息接受一个参数,它是一个键/值对数组 - 这是我遇到的问题。
该消息的 WSDL 是:
<message name='Execute'>
<part name='ContextHandle' type='xsd:string'/>
<part name='ScriptLanguage' type='xsd:string'/>
<part name='Script' type='xsd:string'/>
<part name='Params' type='xsd:anyType'/>
</message>
服务代理类具有以下代码:
[System.Web.Services.WebServiceBindingAttribute(Name="EngineSoapBinding", Namespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/")]
internal partial class Service : System.Web.Services.Protocols.SoapHttpClientProtocol {
....
[System.Web.Services.Protocols.SoapRpcMethodAttribute("http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/action/Execute", RequestNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts", ResponseNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts", Use=SoapBindingUse.Literal)]
[return: System.Xml.Serialization.SoapElementAttribute("Result")]
public object Execute(string ContextHandle, string ScriptLanguage, string Script, object Params) {
object[] results = this.Invoke("Execute", new object[] {
ContextHandle,
ScriptLanguage,
Script,
Params});
return ((object)(results[0]));
}
....
}
在供应商的文档中,Params 参数应该是键/值对的数组。
我已经能够捕获从另一个工作客户端到此服务的网络流量,并获得了该服务识别的以下消息示例(为了清晰起见,删除了 SOAP 信封并对其进行了格式化):
<STES:Execute xmlns:STES="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts" xmlns:sof="http://www.smarteam.com/dev/ns/SOF/2.0">
<ContextHandle>0019469#00228</ContextHandle>
<ScriptLanguage>javascript</ScriptLanguage>
<Script><![CDATA[Context.Result = Context.Params("Number");]]></Script>
<Params SOAP-ENC:arrayType="sof:DictionaryItem[2]">
<sof:DictionaryItem>
<key xsi:type="xsd:string">Number</key>
<value xsi:type="xsd:int">10</value>
</sof:DictionaryItem>
<sof:DictionaryItem>
<key xsi:type="xsd:string">Hello</key>
<value xsi:type="xsd:string">World</value>
</sof:DictionaryItem>
</Params>
</STES:Execute>
我已经尝试了 Params 参数的各种数据结构,但我尝试过的任何方法都无法提供与此 XML 序列化类似的效果。
我能得到的最接近的是编写一个 DictionaryItem
类,它使用以下 WriteXml
方法实现 IXmlSerialized
:
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("key");
writer.WriteValue(Key);
writer.WriteEndElement();
writer.WriteStartElement("value");
writer.WriteValue(Value);
writer.WriteEndElement();
}
然后我给出 < code>Params 参数是一个 List
,它会导致以下在线序列化。
<Execute xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<ContextHandle xmlns="">0022541#00228</ContextHandle>
<ScriptLanguage xmlns="">javascript</ScriptLanguage>
<Script xmlns="">Context.Result = Context.Params("Number");</Script>
<Params xmlns="">
<DictionaryItem xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<key>Number</key>
<value>10</value>
</DictionaryItem>
<DictionaryItem xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<key>Hello</key>
<value>World</value>
</DictionaryItem>
</Params>
</Execute>
谁能指出我正确的方向来使其正常工作?
** 更新 **
对于该供应商使用已弃用的消息格式,我一点也不感到惊讶。他们的整个产品一团糟,如果可以的话我很乐意放弃它。它是用 .Net 编写的,但有一个 COM API,并且此 Web 服务采用已弃用的格式。他们确实为 Web 服务提供了一个客户端库,但它是用 Java 编写的。啊?
我将回到最初的想法,使用 ikvmc 围绕 Java 客户端编写一个包装器。至少我知道我可以让它工作,即使所有类型转换都会很混乱。
至于选择答案,@Cheeso 和 @Aaronaught 都非常有帮助,所以我掷了一枚硬币并将其交给了 @Cheeso。
I'm trying to write a .Net client to a vendor's SOAP Service, but I'm having trouble getting the parameters to the SOAP messages to serialise to a form that the service recognises.
Using wsdl.exe I generated a service proxy class which works fine in itself. However, one of the messages takes an argument which is an array of key/value pairs - this is the bit I'm having problems with.
The WSDL for the message is:
<message name='Execute'>
<part name='ContextHandle' type='xsd:string'/>
<part name='ScriptLanguage' type='xsd:string'/>
<part name='Script' type='xsd:string'/>
<part name='Params' type='xsd:anyType'/>
</message>
The service proxy class has this code:
[System.Web.Services.WebServiceBindingAttribute(Name="EngineSoapBinding", Namespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/")]
internal partial class Service : System.Web.Services.Protocols.SoapHttpClientProtocol {
....
[System.Web.Services.Protocols.SoapRpcMethodAttribute("http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/action/Execute", RequestNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts", ResponseNamespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts", Use=SoapBindingUse.Literal)]
[return: System.Xml.Serialization.SoapElementAttribute("Result")]
public object Execute(string ContextHandle, string ScriptLanguage, string Script, object Params) {
object[] results = this.Invoke("Execute", new object[] {
ContextHandle,
ScriptLanguage,
Script,
Params});
return ((object)(results[0]));
}
....
}
In the vendor's documentation the Params argument should be an array of key/value pairs.
I've been able to capture network traffic from another working client to this service and got the following example of a message that the service recognises (SOAP envelope removed and formatted for clarity):
<STES:Execute xmlns:STES="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts" xmlns:sof="http://www.smarteam.com/dev/ns/SOF/2.0">
<ContextHandle>0019469#00228</ContextHandle>
<ScriptLanguage>javascript</ScriptLanguage>
<Script><![CDATA[Context.Result = Context.Params("Number");]]></Script>
<Params SOAP-ENC:arrayType="sof:DictionaryItem[2]">
<sof:DictionaryItem>
<key xsi:type="xsd:string">Number</key>
<value xsi:type="xsd:int">10</value>
</sof:DictionaryItem>
<sof:DictionaryItem>
<key xsi:type="xsd:string">Hello</key>
<value xsi:type="xsd:string">World</value>
</sof:DictionaryItem>
</Params>
</STES:Execute>
I've tried various data structures for the Params argument, but nothing I've tried gives anything close to this XML serialisation.
The closest I've been able to get is to write a DictionaryItem
class which implements IXmlSerializable
with the following WriteXml
method:
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("key");
writer.WriteValue(Key);
writer.WriteEndElement();
writer.WriteStartElement("value");
writer.WriteValue(Value);
writer.WriteEndElement();
}
I then give the Params
argument a List<DictionaryItem>
which results in the following serialization on the wire.
<Execute xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<ContextHandle xmlns="">0022541#00228</ContextHandle>
<ScriptLanguage xmlns="">javascript</ScriptLanguage>
<Script xmlns="">Context.Result = Context.Params("Number");</Script>
<Params xmlns="">
<DictionaryItem xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<key>Number</key>
<value>10</value>
</DictionaryItem>
<DictionaryItem xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<key>Hello</key>
<value>World</value>
</DictionaryItem>
</Params>
</Execute>
Can anyone point me in the right direction to get this working?
** Update **
It doesn't surprise me in the slightest that this vendor is using a deprecated message format. Their whole product is a mess, and I'd gladly ditch it if I could. It's written in .Net, but has a COM API and this web service in a deprecated format. They do supply a client library for the web service, but it's writtn in Java. Huh?
I'm going to go back to my original idea and write a wrapper around the Java client, using ikvmc. At least I know I can get that to work, even if all the type conversion will be messy.
As for picking an answer @Cheeso and @Aaronaught have both been very helpful, so I've flipped a coin and given it to @Cheeso.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您在那里显示的示例消息显示了一条使用所谓的“SOAP 第 5 节编码”的消息。由于兼容性和互操作性问题,SOAP 编码(更不用说 SOAP)很久以前就被所有主要工具和服务供应商弃用了。就像2004年左右那样。
严重地。没有人应该再使用那些东西了。没有任何借口。
但即便如此,您应该能够让它工作。
面临的挑战是在每个请求和响应元素上获得正确的 XML 命名空间。只需查看示例请求消息(即“有效”的请求消息),您就会发现它有点疯狂。顶级请求元素上有带有 STES 前缀的命名空间 -
Execute
。然后,所有子元素根本没有命名空间。这很奇怪。同样奇怪的开/关名称空间问题也发生在 Params 数组中。包装器元素位于带有
sof
前缀的命名空间中。但键和值子元素不在该命名空间中——它们根本不在命名空间中。在你的尝试中,你会遇到一些不匹配的情况;
就您而言,
DictionaryItem
元素位于http://www.smarteam.com/dev/ns/iplatform/embeddedscripts
命名空间中。它应该位于http://www.smartteam.com/dev/ns/SOF/2.0
命名空间中。在您的情况下,
key
和value
元素位于http://www.smartteam.com/dev/ns/iplatform/embeddedscripts
命名空间。它们根本不应该位于任何命名空间中。通常,正确的 WSDL 意味着您不必担心这些事情。我很困惑为什么您的 WSDL 没有生成执行正确操作的代理。在这种情况下,我建议人们做的是获得真正有效的 wSDL。通常,这意味着在 ASMX 中编写服务的模拟版本。
如果您可以在 ASMX 中生成一个接受真实服务所接受形式的消息的服务,并且如果您可以生成一个与该 ASMX 服务交互的客户端,那么该客户端也应该与真实服务一起工作。我推荐 ASMX 的原因是它很容易调整和重试。
为此,这就是我的想法。
该 ASMX 文件应该生成一个 WSDL 和一个与您的实际服务等效的接口。从它生成 WSDL(使用 ?wsdl 查询),然后编写测试客户端。检查线路上的消息并根据需要进行调整。
您可以看到我将 REAL 类型应用于 Params 数组。我还使用
SoapType
属性修饰该类型并指定所需的 xml 命名空间。在您的问题陈述中,您没有描述响应消息。您需要进行类似的练习 - 调整和调整 - 以便塑造客户“预期”的响应消息,以匹配真实服务实际生成的响应。
另外,请记住 xmlns 前缀并不重要。它是您需要匹配的前缀,它是 XML 名称空间本身。您不需要
STES:Execute
。您可以使用任何命名空间前缀,只要它映射到正确的 xml 命名空间即可。祝你好运。
如果有机会,请说服他们转向符合 WS-I 的服务接口。当服务符合 WS-I 建议时,互操作就会容易得多。
编辑
这是来自客户端的实际消息的跟踪,使用该 WSDL 生成:
即使这与您的目标消息不同,它应该可以由您的服务器解析 -侧面,如果它符合 SOAP v1.1 第 5 节编码规范。此请求消息使用“多重引用”序列化,而您的示例目标消息使用“单一引用”。但它们应该相当于服务器端。应该是。
但正如我最初所说,要使 SOAP 第 5 节编码能够互操作,存在很多问题。
The example message you've shown there shows a message using what is known as "SOAP Section 5 encoding". SOAP encoding (not to say SOAP) was deprecated by all major tools and services vendors a looooong time ago, due to problems with compatibility and interoperability. Like in 2004 or so.
Seriously. Nobody should be using that stuff any longer. There is no excuse.
But even so, you should be able to get it to work.
The challenge is to get the XML namespaces right on each of the request and response elements. Just looking at the example request message - the one that "works" - you can see it's sort of deranged. There's the namespace with the STES prefix on the toplevel request element -
Execute
. Then, all the child elements get no namespace at all. This is weird.The same weird on/off namespace thing occurs in the Params array. The wrapper element is in the namespace with the
sof
prefix. But the key and value child elements are not in that namespace- they are in no namespace at all.In your attempt, you have a couple mismatches then;
In your case, the
DictionaryItem
element is in thehttp://www.smarteam.com/dev/ns/iplatform/embeddedscripts
namespace. It should be in thehttp://www.smarteam.com/dev/ns/SOF/2.0
namespace.In your case, the
key
andvalue
elements are in thehttp://www.smarteam.com/dev/ns/iplatform/embeddedscripts
namespace. They should be in no namespace at all.Normally a proper WSDL means you do not have to worry about any of these things. I am puzzled as to why your WSDL is not generating a proxy that does the right thing. What I recommend people do in this case is get a wSDL that really works. Oftentimes that means writing a mock version of the service in ASMX.
If you can generate a service in ASMX that accepts messages of the form accepted by the real service, and if you can generate a client that interacts with that ASMX service, then the client should also work with the real service. the reason I recommend ASMX is that it's so easy to tweak and retry things.
Toward that end, here's what I cam eup with.
This ASMX file should produce a WSDL and an interface that is equivalent to your real service. Generate the WSDL from it (using the ?wsdl query), and then write a test client. Examine the messages on the wire and tweak as necessary.
You can see that I applied a REAL type to the Params array. Also I decorated that type with the
SoapType
attribute and specified the desired xml namespace.In your problem statement you didn't describe the response message. You'll need to go through a similar exercise - tweaking and adjusting - in order to shape the response message "expected" by your client to match the responses actually generated by the real service.
Also, remember that the xmlns prefixes are not significant. It's nto the prefix you need to match, it's the XML namespace itself. You don't need
STES:Execute
. You can use any namespace prefix, as long as it maps to the correct xml namespace.Good luck.
If you get a chance, convince them to move to a WS-I compliant service interface. Interop is much easier when the service complies with the WS-I recommendations.
EDIT
This is a trace of the actual message from the client, generated using that WSDL:
Even though this is different than your target message, this should be parseable by your server-side, if it conforms to SOAP v1.1 section 5 encoding spec. This request message uses the "multiple reference" serialization whereas your example target message uses "single reference". But they should be equivalent to the server side. Should be.
But as I said originally, there were lots of problems getting SOAP section 5 encoding to work interoperably.
WCF 解决方案确实相当简单,只需在导入中使用这些类:
我正在使用 Marc Gravell 的 CDataWrapper 此处强制 CDATA 标签围绕
Script
。DataContractSerializer
将生成与您已经通过网络看到的内容几乎相同的输出:唯一潜在的问题是
ArrayOfDictionaryItem
,它是.NET 似乎总是对数组类型使用这种约定。如果您实际查看为这些类型生成的 WSDL,您会发现它实际上引用soapenc:arrayType,但如果端点是不知道这个约定。如果是这种情况,那么不幸的是,我认为您必须求助于 IXmlSerialized,因为我一直无法找到一种方法来禁用 ArrayOf 生成。网。正如 Cheeso 提到的,RPC/编码的 SOAP 格式已被 WS-I 正式弃用,并且永远不应再在生产服务中使用。它被弃用的原因之一是它的互操作性很差并且实施起来很痛苦。如果可能的话,您确实应该与供应商讨论获取更新,更新应该使用标准文档/文字传输格式。
The WCF solution is really rather simple, just use these classes in your import:
I'm using Marc Gravell's CDataWrapper here to force the CDATA tags around the
Script
.The
DataContractSerializer
will generate output that's nearly identical to what you're seeing over the wire already:The only potential problem is the
ArrayOfDictionaryItem
, which is a convention that .NET always seems to use for array types. If you actually look at the generated WSDL for these types, you'll see that it actually references thesoapenc:arrayType
, but that may not be sufficient here if the endpoint is unaware of this convention. If that is the case, then unfortunately I think you'll have to resort toIXmlSerializable
, because I've never been able to find a way to disable theArrayOf
generation in .NET.As Cheeso mentions, the RPC/encoded SOAP format is officially deprecated by the WS-I and should never be used in production services anymore. One of the reasons it was deprecated was because it was lousy for interop and painful to implement. If possible, you really should talk to the vendor about getting an update, which ought to be using the standard document/literal wire format.