【问题标题】:Creating XML childs, omit parent node创建 XML 子节点,省略父节点
【发布时间】:2020-01-09 07:24:38
【问题描述】:

根据 MSDN 文档:

您还可以通过将 XmlElementAttribute 应用于返回数组的字段,将数组序列化为 XML 元素的平面序列,如下所示。

所需的 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-an-array-as-a-sequence-of-elements

如何标记我的 C# 类和属性,以便 WSDL 输出看起来如上(和文档)?

【问题讨论】:

  • 是否有任何带有 xsd 定义的 wsdl 可用? PHP 的 SoapClient 类有点棘手。通常你必须使用 PHP SoapVar 对象来获得正确的 xml 结构。
  • 有来自世界标准创建者 (ediWheel btw) 的 XSD,但正如我使用 xsd2code 所了解的那样 - 这些 xsd 可以通过多种方式进行解释。只是为了澄清。我不是做 PHP 部分的人。我们的客户是。我返回强类型 DataClass。我使用 WSDLBrowser.com 进行测试
  • 或者您是否问过我 WSDL 在某处是否可用?如果是的话,是的。你甚至可以从我发布的网络服务中自己获取它:)
  • 上面显示的 PHP 输出是您的客户端正在创建的请求,还是您的服务发送回您的客户端的响应?
  • 我专注于响应。然而;我尝试使用和不使用 parent ORDERLINE 调用我的 SOAP 服务。无论哪种方式都可以正常工作。

标签: c# xml soap


【解决方案1】:

TL;DR 您的问题是 .NET 有两个单独的 XML 序列化程序,XmlSerializerDataContractSerializer,而您正在创建一个 WCF 服务,uses DataContractSerializer by default。您需要通过将XmlSerializerFormatAttribute 应用到您的service contract 来切换到使用XmlSerializer

详情如下。假设您有以下 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 具有包含重复序列的 &lt;ResponseLineTest&gt; 元素的架构,例如像下面这样:

  <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")] 属性被完全忽略了。为什么会这样?

事实证明,这种行为记录在 Using the XmlSerializer Class 中,这说明您的服务根本没有使用 XmlSerializer,而是使用了一个忽略 [XmlElement] 属性的不同序列化程序:

默认情况下,WCF 使用 DataContractSerializer 类来序列化数据类型

&lt;&lt;snip&gt;&gt;

有时,您可能必须手动切换到 XmlSerializer。例如,在以下情况下会发生这种情况:

  • 当精确控制出现在消息中的 XML 很重要时...

在这种情况下,需要对 XML 进行精确控制,特别是强制在没有外部容器元素的情况下序列化 ResponseLine 属性。但是,序列化没有外部容器元素的集合是not supported by DataContractSerializer,因此您必须通过将[XmlSerializerFormat] 应用于您的服务合同来切换到使用XmlSerializer

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

现在为您的服务生成的 WSDL 将是您所需要的。

【讨论】:

  • 男人;你拯救了我的一天、一周甚至整个月。我已经盯着这个问题一个多星期了。质疑我的理智等。它当然有效。我现在遇到了各种其他更自然的命名空间问题。但这些都是可以修复的。尤其是现在它的行为符合文档!
【解决方案2】:

您的网络服务定义中的必要更改

首先,必要的复杂类型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>

这意味着您可以另外发送所有内容。您的 web 服务不知道 PlaceOrder 上下文中的 OrderLine 元素,因为它没有在此处定义。您必须将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>

这个定义说,你想要有一个父节点OrderLineOrderLine 复杂类型。所以父节点完全按照您的 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 值对象。如您所见,在您的 Web 服务定义中定义的所有元素都作为此类的属性出现。此类是PlaceOrder 复杂类型的精确php 实现。您可以说所有复杂类型始终是 PHP 类。此外,接受的方法参数主要是SoapVar 实例。这对于s​​oap客户端很重要,因为这保证了最后正确的xml结构。

OrderLine 值对象 ...

<?php
namespace Webservice\Entity;

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

    // getters and setters here
}

通过这两个类,可以使用 PHP 进行完整的 Web 服务调用。以下示例不是 testet,它显示了如何使用 PHP SoapClient 类。这门课有时有点欺骗性,最后需要一些工作才能得到正确的结果。但主要是这就是它的工作方式。

<?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 "</pre>";
} catch (SoapFault $fault) {
    echo "<pre>";
    var_dump($fault);
    echo "</pre>";
}

结论

您的网络服务定义非常不精确。出于这个原因,您应该简单地重新考虑参数的定义,并在 WSDL 文件中更准确地定义它们。然后它也适用于 PHP。 PHP 在其 soap 扩展中使用严格的 Web 标准。

【讨论】:

  • 天哪;恐怕我整天都在玩弄那个 WSDL,每分钟都有很多版本,有些定义了 orderline,有些没有调试不同的场景。订单线现在在那里。我为了调试,添加了一个非常简单的方法,叫做 PlaceOrderTestNoInput。您能否检查此方法是否具有相同的不精确定义?
  • 你把它弄得不好,对此我深表歉意。我在 orderline 上看到了你的建议。正如你所描述的,我遗漏了一些明显的东西。
  • 我刚刚浏览了最新的 WSDL。我现在说 PlaceOrder 和你描述的完全一样。但无济于事。
  • 好吧,在您的示例中,您希望 OrderLine 节点可以具有以下状态:可空(请求中未提及)、零次或 n 次出现。因此,您可以在 xsd 定义中添加以下属性:&lt;... nillable="true"&gt;(元素可以为空)、&lt;... minOccurs="0"&gt;(元素必须出现 0 次)和&lt;... maxOccurs="unbounded"&gt;(元素可以出现 n 次)。不要将 maxOccurs 属性更改为 0。这意味着该元素最多可能出现 0 次。
  • 谢谢你,你帮了我很多,确认了 WSDL 应该是什么样子,这让我放心。它没有改变 PHP SOAP 客户端的输出。正如他们所说,我们回到了起点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-27
  • 1970-01-01
相关资源
最近更新 更多