Сериализация параметров SOAP в C # - PullRequest
3 голосов
/ 24 октября 2011

Я пытаюсь записать .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 и этот веб-сервис в устаревшем формате. Они предоставляют клиентскую библиотеку для веб-службы, но она написана на Java. А?

Я собираюсь вернуться к своей первоначальной идее и написать оболочку для Java-клиента, используя ikvmc. По крайней мере, я знаю, что смогу заставить это работать, даже если все преобразования типов будут грязными.

Что касается выбора ответа, @Cheeso и @Aaronaught были очень полезны, поэтому я подбросил монету и отдал ее @ Cheeso.

Ответы [ 2 ]

2 голосов
/ 24 октября 2011

Решение 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; }
}

Я использую CDataWrapper Марка Грэвелла, чтобы навязать теги CDATA вокруг Script.

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, потому что я так и не смог найти способ отключить генерацию ArrayOf в .NET.

Как упоминает Cheeso, формат RPC / кодированный SOAP официально устарел в WS-I и больше не должен использоваться в производственных сервисах. Одна из причин , которую он устарел, заключалась в том, что он был паршивым для взаимодействия и болезненным для реализации. Если возможно, вам действительно следует поговорить с поставщиком о получении обновления, в котором должен использоваться стандартный формат документа / литерала.

2 голосов
/ 24 октября 2011

В приведенном вами примере сообщения показано сообщение с использованием так называемой «кодировки SOAP Раздел 5».SOAP-кодирование (не говоря уже о SOAP) было объявлено устаревшим всеми основными поставщиками инструментов и сервисов довольно давно, из-за проблем с совместимостью и совместимостью.Как в 2004 году или около того.

Серьезно.Никто не должен больше использовать этот материал.Нет оправдания.

Но даже в этом случае, вы должны быть в состоянии заставить его работать.

Задача состоит в том, чтобы правильно получить пространства имен XML для каждого из элементов запроса и ответа.Просто взглянув на пример сообщения с запросом - тот, который «работает» - вы можете увидеть, что он как бы ненормальный.В элементе запроса верхнего уровня есть пространство имен с префиксом STES - Execute.Затем все дочерние элементы вообще не получают пространства имен.Это странно

То же самое странное включение / выключение пространства имен происходит в массиве Params.Элемент оболочки находится в пространстве имен с префиксом sof.Но дочерние элементы ключа и значения не находятся в этом пространстве имен - они вообще не находятся в пространстве имен.

В вашей попытке у вас есть пара несовпадений;

Обычно правильный 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.


EDIT

Это трассировка фактического сообщения от клиента, сгенерированного с использованием этого 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.В этом сообщении-запросе используется сериализация «множественная ссылка», в то время как в вашем примере целевого сообщения используется «одиночная ссылка».Но они должны быть эквивалентны стороне сервера.Должно быть.

Но, как я уже сказал, было много проблем с обеспечением совместимости кодирования раздела 5 SOAP.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...