我正在尝试将.Net客户端写入供应商的SOAP服务,但是我无法将参数传递给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
方法编写IXmlSerializable
类来实现WriteXml
:
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("key");
writer.WriteValue(Key);
writer.WriteEndElement();
writer.WriteStartElement("value");
writer.WriteValue(Value);
writer.WriteEndElement();
}
然后我给Params
参数List<DictionaryItem>
,这导致了以下序列化。
<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。
答案 0 :(得分:2)
您在此处显示的示例消息显示了使用所谓的“SOAP Section 5编码”的消息。由于兼容性和互操作性方面的问题,所有主要工具和服务供应商在很久以前就不推荐使用SOAP编码(更不用说SOAP)了。就像在2004年那样。
严重。没人应该再使用那些东西了。没有任何借口。
但即便如此,你应该能够让它发挥作用。
面临的挑战是在每个请求和响应元素上使用XML命名空间。只是查看示例请求消息 - “工作”的消息 - 您可以看到它有点疯狂。在顶层请求元素Execute
上有一个带有STES前缀的命名空间。然后,所有子元素都没有命名空间。这很奇怪。
Params数组中出现了同样奇怪的开/关命名空间。包装器元素位于名称空间中,带有sof
前缀。但是key和value子元素不在该命名空间中 - 它们根本没有命名空间。
在你的尝试中,你会遇到几个错配;
在您的情况下,DictionaryItem
元素位于http://www.smarteam.com/dev/ns/iplatform/embeddedscripts
命名空间中。它应该在http://www.smarteam.com/dev/ns/SOF/2.0
命名空间中。
在您的情况下,key
和value
元素位于
http://www.smarteam.com/dev/ns/iplatform/embeddedscripts
命名空间。它们应该没有名称空间。
通常,正确的WSDL意味着您不必担心这些事情。我很困惑为什么你的WSDL没有生成一个做正确事情的代理。我建议人们在这种情况下做的是获得真正有效的wSDL。通常这意味着在ASMX中编写服务的模拟版本。
如果您可以在ASMX中生成接受真实服务接受的表单的消息的服务,并且您可以生成与该ASMX服务交互的客户端,那么客户端也应该使用真实服务。我推荐ASMX的原因是它很容易调整和重试。
为此,这就是我所追求的目标。
<%@ WebService Language="c#"
Class="Cheeso.CooperService"
%>
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using System.Collections;
namespace Cheeso
{
[SoapType(Namespace="http://www.smarteam.com/dev/ns/SOF/2.0")]
public class DictionaryItem
{
public string key { get; set; }
public string value { get; set; }
}
[System.Web.Services.WebService
(Name="EngineSoapBinding",
Namespace="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/")]
public class CooperService : System.Web.Services.WebService
{
[WebMethod]
[SoapRpcMethod
("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.Encoded)]
[return: System.Xml.Serialization.SoapElementAttribute("Result")]
public object Execute(string ContextHandle,
string ScriptLanguage,
string Script,
DictionaryItem[] Params)
{
return "The answer is 42. What is the question?";
}
}
}
此ASMX文件应生成一个WSDL和一个与您的实际服务等效的接口。从中生成WSDL(使用?wsdl查询),然后编写测试客户端。检查线路上的消息并根据需要进行调整。
你可以看到我将一个REAL类型应用于Params数组。我还使用SoapType
属性修饰了该类型,并指定了所需的xml命名空间。
在您的问题陈述中,您没有描述响应消息。您需要进行类似的练习 - 调整和调整 - 以便塑造客户“预期”的响应消息,以匹配实际服务实际生成的响应。
另外,请记住xmlns前缀不重要。它是你需要匹配的前缀,它是XML命名空间本身。您不需要STES:Execute
。您可以使用任何名称空间前缀,只要它映射到正确的xml名称空间。
如果您有机会,请说服他们转移到符合WS-I的服务界面。当服务符合WS-I建议时,Interop会更容易。
修改强>
这是来自客户端的实际消息的跟踪,使用该WSDL生成:
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/"
xmlns:types="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts/wsdl/encodedTypes"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<q1:Execute xmlns:q1="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts">
<ContextHandle xsi:type="xsd:string">00913983</ContextHandle>
<ScriptLanguage xsi:type="xsd:string">Canadian, eh?</ScriptLanguage>
<Script xsi:type="xsd:string">To be or not to be....</Script>
<Params href="#id1" />
</q1:Execute>
<soapenc:Array id="id1"
xmlns:q2="http://www.smarteam.com/dev/ns/SOF/2.0"
soapenc:arrayType="q2:DictionaryItem[2]">
<Item href="#id2" />
<Item href="#id3" />
</soapenc:Array>
<q3:DictionaryItem id="id2"
xsi:type="q3:DictionaryItem"
xmlns:q3="http://www.smarteam.com/dev/ns/SOF/2.0">
<key xsi:type="xsd:string">17</key>
<value xsi:type="xsd:string">s9dkjdls</value>
</q3:DictionaryItem>
<q4:DictionaryItem id="id3"
xsi:type="q4:DictionaryItem"
xmlns:q4="http://www.smarteam.com/dev/ns/SOF/2.0">
<key xsi:type="xsd:string">fish</key>
<value xsi:type="xsd:string">barrel</value>
</q4:DictionaryItem>
</soap:Body>
</soap:Envelope>
即使这与目标消息不同,如果服务器端符合SOAP v1.1第5节编码规范,则应该可由服务器端解析。此请求消息使用“多个引用”序列化,而您的示例目标消息使用“单个引用”。但它们应该等同于服务器端。应该。
但正如我原先所说,让SOAP第5节编码互操作有很多问题。
答案 1 :(得分:2)
WCF解决方案非常简单,只需在导入中使用这些类:
[DataContract(Namespace = "http://www.smarteam.com/dev/ns/iplatform/embeddedscripts")]
[KnownType(typeof(SofDictionaryItem[]))]
[XmlSerializerFormat(Style = OperationFormatStyle.Rpc, Use = OperationFormatUse.Encoded)]
public class Execute
{
[DataMember(Order = 0)]
public string ContextHandle { get; set; }
[DataMember(Order = 1)]
public string ScriptLanguage { get; set; }
public string Script { get; set; }
[DataMember(Name = "Script", Order = 2, EmitDefaultValue = false)]
private CDataWrapper ScriptCData
{
get { return Script; }
set { Script = value; }
}
[DataMember(Order = 3)]
public object Params { get; set; }
}
[DataContract(Namespace = "http://www.smarteam.com/dev/ns/SOF/2.0", Name = "DictionaryItem")]
public class SofDictionaryItem
{
[DataMember]
public object Key { get; set; }
[DataMember]
public object Value { get; set; }
}
我在这里使用Marc Gravell的CDataWrapper来强制Script
周围的CDATA标记。</ p>
DataContractSerializer
将生成的输出几乎与您在线上看到的相同
<Execute xmlns="http://www.smarteam.com/dev/ns/iplatform/embeddedscripts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ContextHandle>0019469#00228</ContextHandle>
<ScriptLanguage>javascript</ScriptLanguage>
<Script><![CDATA[Context.Result = Context.Params("Number")]]></Script>
<Params i:type="a:ArrayOfDictionaryItem" xmlns:a="http://www.smarteam.com/dev/ns/SOF/2.0">
<a:DictionaryItem>
<a:Key i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Number</a:Key>
<a:Value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">10</a:Value>
</a:DictionaryItem>
<a:DictionaryItem>
<a:Key i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">Hello</a:Key>
<a:Value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">World</a:Value>
</a:DictionaryItem>
</Params>
</Execute>
唯一可能的问题是ArrayOfDictionaryItem
,这是.NET似乎总是用于数组类型的约定。如果您实际查看为这些类型生成的WSDL,您将看到它实际上引用 soapenc:arrayType
,但如果端点不知道此约定,则可能不够。如果是这种情况,那么不幸的是我认为你将不得不诉诸IXmlSerializable
,因为我从来没有找到过在.NET中禁用ArrayOf
代的方法。
正如Cheeso所提到的,RPC /编码的SOAP格式已被WS-I正式弃用,不应再用于生产服务。其中一个原因被弃用是因为它很难实现互操作而且实施起来很痛苦。如果可能,您真的应该与供应商讨论获取更新的问题,该更新应该使用标准文档/文字线格式。