Создание XML дочерних элементов, опустить родительский узел - PullRequest
1 голос
/ 09 января 2020

В соответствии с документами MSDN:

Вы также можете сериализовать массив в виде плоской последовательности элементов XML, применив атрибут XmlElementAttribute к полю, возвращающему массив следующим образом.

Желаемая XML схема:

<xs:element minOccurs="0" maxOccurs="unbounded" name="ResponseLineTest" type="OrderLn" />

C#:

 [XmlElementAttribute("ResponseLineTest")]
 public OrderLn[] ResponseLine { get; set; }

Однако: Использование. NET 4.51 Я получаю эту схему:

 <xs:element minOccurs="0" name="ResponseLine" nillable="true" type="tns:ArrayOfOrderLn"/>

https://docs.microsoft.com/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes#serializing-массив-как-последовательность-последовательность элементов

КАК пометить мой C# классы и свойства, поэтому вывод WSDL выглядит так, как указано выше (и документация)?

Ответы [ 2 ]

1 голос
/ 14 января 2020

TL; DR Ваша проблема в том, что. NET имеет два отдельных XML сериализатора, XmlSerializer и DataContractSerializer, и вы создают службу WCF, которая использует DataContractSerializer по умолчанию . Вам необходимо перейти на использование XmlSerializer, применив XmlSerializerFormatAttribute к вашему контракту на обслуживание .

Подробности следующим образом. Скажите, что у вас есть следующий контракт на обслуживание WCF (не показан в вашем вопросе):

public class Output
{
    [XmlElementAttribute("ResponseLineTest")]
    public OrderLn[] ResponseLine { get; set; }
}

public class OrderLn
{
    public string Order { get; set; }
}

[ServiceContract(Namespace = "Question59659046")]
[XmlSerializerFormat]
public interface IQuestion59659046Service
{
    [OperationContract]
    Output GetOutput(string input);
}

Вы хотели бы, чтобы XML, сгенерированный этой службой, имел схему, которая включает в себя повторяющуюся последовательность элементов <ResponseLineTest>, например, как следующее:

  <xs:complexType name="Output">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="unbounded" name="ResponseLineTest" type="tns:OrderLn" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="OrderLn">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="1" name="Order" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

И поэтому вы применяете атрибут XmlElement к ResponseLine, чтобы заставить его сериализоваться в XML таким образом. Но когда вы генерируете WSDL для своей службы, вы вместо этого получаете схему, которая выглядит следующим образом:

  <xs:complexType name="Output">
    <xs:sequence>
      <xs:element minOccurs="0" name="ResponseLine" nillable="true" type="tns:ArrayOfOrderLn" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="ArrayOfOrderLn">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="unbounded" name="OrderLn" nillable="true" type="tns:OrderLn" />
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="OrderLn">
    <xs:sequence>
      <xs:element minOccurs="0" name="Order" nillable="true" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="OrderLn" nillable="true" type="tns:OrderLn" />

Схема содержит дополнительный промежуточный тип ArrayOfOrderLn, а переопределенное имя элемента "ResponseLineTest" не использовалось , Очевидно, атрибут [XmlElementAttribute("ResponseLineTest")] был полностью проигнорирован. Почему это может быть?

Как выясняется, это поведение описано в Использование класса XmlSerializer , что объясняет, что ваша служба не использует XmlSerializer в все, а скорее другой сериализатор, который игнорирует атрибут [XmlElement]:

По умолчанию WCF использует класс DataContractSerializer для сериализации типов данных .

<<snip>>

Иногда вам может потребоваться вручную переключиться на XmlSerializer. Это происходит, например, в следующих случаях:

  • Когда важен точный контроль над XML, который появляется в сообщениях ...

In в этом случае требуется точный контроль над XML , особенно для принудительной сериализации свойства ResponseLine без внешнего элемента контейнера. Однако сериализация коллекции без внешнего элемента контейнера не поддерживается DataContractSerializer, поэтому вы должны перейти на использование XmlSerializer, применив [XmlSerializerFormat] к своему контракту на обслуживание:

[ServiceContract(Namespace = "Question59659046")]
[XmlSerializerFormat]
public interface IQuestion59659046Service
{
    [OperationContract]
    Output GetOutput(string input);
}

Теперь WSDL, сгенерированный для вашей службы, будет соответствовать требованиям.

1 голос
/ 10 января 2020

Необходимые изменения в определении вашего веб-сервиса

Прежде всего, определение необходимого комплексного типа PlaceOrder не затрагивает элемент типа tns:OrderLine. Я думаю, вы должны изменить определение элемента. Поскольку ваш веб-сервис очень слабо определен, он работает так, как вы показали в своем вопросе.

Вот текущее определение запроса PlaceOrder. Это говорит о том, что в качестве параметра запроса требуется элемент PlaceOrder.

<wsdl:message name="IService_PlaceOrder_InputMessage">
    <wsdl:part name="parameters" element="tns:PlaceOrder"/>
</wsdl:message>

Текущее определение сложного типа PlaceOrder показывает, что элемента OrderLine нет.

<xs:element name="PlaceOrder">
    <xs:complexType>
        <xs:sequence>
            <xs:element minOccurs="0" name="userToken" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="ediOrder" nillable="true" type="q1:order"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Это означает, что вы можете отправить все дополнительно. Ваш веб-сервис не знает элемент OrderLine в контексте PlaceOrder, поскольку он здесь не определен. Вы должны изменить определение элемента PlaceOrder в следующих обозначениях.

<xs:element name="PlaceOrder">
    <xs:complexType>
        <xs:sequence>
            <xs:element minOccurs="0" name="userToken" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="ediOrder" nillable="true" type="q1:order"/>
            <xs:element minOccurs="0" name="OrderLine" nillable="true" type="q1:ArrayOfOrderLine"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Определение ArrayOfOrderLine определяется следующим образом:

<xs:complexType name="ArrayOfOrderLine">
    <xs:sequence>
        <xs:element minOccurs="0" maxOccurs="unbounded" name="OrderLine" nillable="true" type="tns:OrderLine"/>
    </xs:sequence>
</xs:complexType>

Это определение говорит о том, что вы хотите OrderLine составные типы с родительским узлом OrderLine. Таким образом, родительский узел происходит точно так, как определено в вашем файле wsdl. Чтобы опустить родительский узел, необходимо переопределить сложный тип PlaceOrder следующим образом:

<xs:element name="PlaceOrder">
    <xs:complexType>
        <xs:sequence>
            <xs:element minOccurs="0" name="userToken" nillable="true" type="xs:string"/>
            <xs:element minOccurs="0" name="ediOrder" nillable="true" type="q1:order"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" name="OrderLine" nillable="true" type="tns:OrderLine"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Это новое определение показывает, что элементу OrderLine нельзя присвоить имя или его можно назвать несколько раз. В этом случае родительским узлом является PlaceOrder.

Возможный PHP пример

Soap следует строго объектно-ориентированному подходу. Исходя из этого понимания, вам также придется работать с объектами в PHP. Сначала вам нужны объекты значений (иногда называемые сущностями) на основе вашего определения xsd / wsdl. Помните, что в этом примере используется переопределенное определение PlaceOrder.

<?php
namespace Webservice\Entity;
use ArrayObject;
use SoapVar;

class PlaceOrder
{
    protected $userToken;
    protected $ediOrder;
    protected $OrderLine;

    public function __construct()
    {
        $this->OrderLine = new ArrayObject();
    }

    public function getUserToken(): ?SoapVar
    {
        return $this->userToken;
    }

    public function setUserToken(SoapVar $userToken): self
    {
        $this->userToken = $userToken;
        return $this;
    }

    public function getEdiOrder() : ?SoapVar
    {
        return $this->ediOrder;
    }

    public function setEdiOrder(SoapVar $ediOrder): self
    {
        $this->ediOrder = $ediOrder;
        return $this;
    }

    public function getOrderLine(): ArrayObject
    {
        return $this->OrderLine;
    }

    public function attachOrderLine(SoapVar $orderLine): self
    {
        $this->orderLine->append($orderLine);
        return $this;
    }

    public function setOrderLine(ArrayObject $orderLine): self
    {
        $this->OrderLine = $orderLine;
        return $this;
    }
}

Показанный выше код PHP показывает объект значения PlaceOrder. Как вы можете видеть, все элементы, которые определены в вашем определении веб-сервиса, встречаются как свойства этого класса. Этот класс является точной php реализацией сложного типа PlaceOrder. Можно сказать, что все сложные типы всегда являются PHP классами. Далее принятыми параметрами метода являются в основном SoapVar экземпляры. Это важно для клиента soap, потому что это гарантирует правильную структуру xml в конце.

Объект значения OrderLine ...

<?php
namespace Webservice\Entity;

class OrderLine 
{
    protected $AdditionalCustomerReferenceNumber;
    protected $LineID;
    protected $OrderedArticle;
    protected $PortalReference;

    // getters and setters here
}

С этими двумя классами можно сделать полный вызов веб-службы с PHP. Следующий пример не является тестом и показывает, как работать с классом PHP SoapClient. Класс иногда немного обманчив, и для достижения правильного результата в конце требуется немало работы. Но в основном это то, как это работает.

<code><?php
namespace Wesbervice;
use Webservice\Entity\Order;
use Webservice\Entity\OrderLine;
use Webservice\Entity\PlaceOrder;
use SoapFault;
use SoapVar;

try {
    // this url contains the wrong defined PlaceOrder complex type
    $wsdl = 'https://uat-salesapi.ndias.com/service.svc?singlewsdl&version=27';

    $client = new SoapClient($wsdl, [
        'cache_wsdl' => WSDL_CACHE_NONE, // as long as you work on your wsdl
        'encoding' => 'utf-8',
        'exceptions' => true,
        'soap_version' => SOAP_1_1,
        'trace' => true, // enables tracing and __getLastRequest() / __getLastResponse()
        'classmap' => [
            'order' => Order::class,
            'OrderLine' => OrderLine::class,
            'PlaceOrder' => PlaceOrder::class,    
        ],
    ]);

    // user token
    $usertoken = new SoapVar('bla', XSD_STRING, '', '', 'userToken', 'http://tempuri.org/');

    // edi order
    $order = (new Order())
        ->setBlanketOrderReference(new SoapVar(...))
        ->setBuyerParty(new SoapVar(...);

    $order = new SoapVar($order, SOAP_ENC_OBJECT, '', '', 'ediOrder', 'http://tempuri.org/');

    // compile our request parameter
    $parameter = (new PlaceOrder())
        ->setUserToken($usertoken)
        ->setEdiOrder($order);

    // order list objects
    $orderLine1 = (new OrderLine())
        ->setAdditionalCustomerReferenceNumber(new SoapVar(...))
        ->setLineID(new SoapVar(...));

    $orderLine1 = new SoapVar($orderLine1, SOAP_ENC_OBJECT, '', '', 'OrderLine', 'http://tempuri.org/');

    $parameter->attachOrderLine($orderLine1);

    $orderLine2 = (new OrderLine())
        ->setAdditionalCustomerReferenceNumber(new SoapVar(...))
        ->setLineID(new SoapVar(...));

    $orderLine2 = new SoapVar($orderLine2, SOAP_ENC_OBJECT, '', '', 'OrderLine', 'http://tempuri.org/');

    $parameter->attachOrderLine($orderLine2);

    $parameter = new SoapVar($parameter, SOAP_ENC_OBJECT, '', '', 'PlaceOrder', 'http://tempuri.org/');

    // the client knows the PlaceOrder method from the wsdl
    $result = $client->PlaceOrder($parameter);

    // the result is a stdClass structure, als long as the classmap parameter does not contain definitions of type to php entity classes
    echo "<pre>";
    var_dump($result);
    echo "
";} catch (SoapFault $ fault) {echo"
";
    var_dump($fault);
    echo "
";}

Заключение

Ваш веб-сервис очень неточно определен. По этой причине вам следует просто переосмыслить определения параметров и более точно определить их в файле WSDL. Затем он также работает с PHP. PHP использует строго веб-стандарты в своем soap расширении.

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