在C#中序列化SOAP参数

时间:2011-10-24 01:52:38

标签: c# soap

我正在尝试将.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。

2 个答案:

答案 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命名空间中。

  • 在您的情况下,keyvalue元素位于 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正式弃用,不应再用于生产服务。其中一个原因被弃用是因为它很难实现互操作而且实施起来很痛苦。如果可能,您真的应该与供应商讨论获取更新的问题,该更新应该使用标准文档/文字线格式。