Accordingly to MSDN docs:
You can also serialize an array as a flat sequence of XML elements by applying a XmlElementAttribute to the field returning the array as follows.
Desired XML schema:
<xs:element minOccurs="0" maxOccurs="unbounded" name="ResponseLineTest" type="OrderLn" />
C#:
[XmlElementAttribute("ResponseLineTest")]
public OrderLn[] ResponseLine { get; set; }
However: Using .NET 4.51 I get this schema:
<xs:element minOccurs="0" name="ResponseLine" nillable="true" type="tns:ArrayOfOrderLn"/>
https://learn.microsoft.com/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes#serializing-an-array-as-a-sequence-of-elements
HOW do I mark my C# classes and properties, so the WSDL output looks as above (and the documentation) ?
TL;DR Your problem is that .NET has two separate XML serializers, XmlSerializer and DataContractSerializer, and you are creating a WCF service, which uses DataContractSerializer by default. You need to switch to using XmlSerializer by applying XmlSerializerFormatAttribute to your service contract.
Details as follows. Say you have the following WCF service contract (not shown in your question):
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);
}
You would like the XML generated by this service to have a schema that includes a repeating sequence of <ResponseLineTest> elements, e.g. like the following:
<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>
And so you apply the XmlElement attribute to ResponseLine to force it to be serialized to XML in this manner. But, when you generate a WSDL for your service, you instead get a schema that looks like:
<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" />
The schema includes an extra intermediate type ArrayOfOrderLn and the overridden element name "ResponseLineTest" was not used. Apparently the [XmlElementAttribute("ResponseLineTest")] attribute was completely ignored. Why might this be?
As it turns out, this behavior is documented in Using the XmlSerializer Class, which explains that your service is not using XmlSerializer at all, but rather a different serializer that ignores the [XmlElement] attribute:
By default WCF uses the DataContractSerializer class to serialize data types.
<<snip>>
At times, you may have to manually switch to the XmlSerializer. This happens, for example, in the following cases:
When precise control over the XML that appears in messages is important...
In this case, precise control over the XML is required, specifically to force the ResponseLine property to be serialized without an outer container element. However, serializing a collection without an outer container element is not supported by DataContractSerializer, so you must switch to using XmlSerializer by applying [XmlSerializerFormat] to your service contract:
[ServiceContract(Namespace = "Question59659046")]
[XmlSerializerFormat]
public interface IQuestion59659046Service
{
[OperationContract]
Output GetOutput(string input);
}
Now the WSDL generated for your service will be as required.
Necessary changes in your webservice definition
First of all the definition of the necessary complex type PlaceOrder does not mention an element of the type tns:OrderLine. I guess you have to change the definition of the element. Because your webservice is very loosely defined, it works like you 've shown in your question.
Here 's the current definition of the PlaceOrder request. This says, that the PlaceOrder element is required as request parameter.
<wsdl:message name="IService_PlaceOrder_InputMessage">
<wsdl:part name="parameters" element="tns:PlaceOrder"/>
</wsdl:message>
The current definition of the PlaceOrder complex type shows, that there is no OrderLine element.
<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>
This means, that you can send everything in addition. Your webservice does not know an OrderLine element in the PlaceOrder context because it is not defined here. You have to change the definition of the PlaceOrder element into the following notation.
<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>
The definition of ArrayOfOrderLine is defined as follows:
<xs:complexType name="ArrayOfOrderLine">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="OrderLine" nillable="true" type="tns:OrderLine"/>
</xs:sequence>
</xs:complexType>
This definition says, that you want the OrderLine complex types with a parent node OrderLine. So the parent node occurs exactly as defined in your wsdl file. To omit the parent node you have to redefine the PlaceOrder complex type as follows:
<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>
This new definition shows that the "OrderLine" element cannot be named or can be named more than once. The parent node in this case is PlaceOrder.
A possible PHP example
Soap follows a strictly object-oriented approach. Based on this understanding, you also have to work with objects in PHP. First you need value objects (sometimes called entities) based on your xsd/wsdl definition. Keep in mind, that this example uses the redefined PlaceOrder definition.
<?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;
}
}
The above shown PHP code shows the PlaceOrder value object. As you can see all elements, which are defined in your webservice definition, occur as properties of this class. This class is an exact php implementation of the PlaceOrder complex type. You can say that all complex types are always PHP classes. Further the accepted method parameters are mainly SoapVar instances. This is important for the soap client because this guarantees the right xml structure at the end.
The OrderLine value object ...
<?php
namespace Webservice\Entity;
class OrderLine
{
protected $AdditionalCustomerReferenceNumber;
protected $LineID;
protected $OrderedArticle;
protected $PortalReference;
// getters and setters here
}
With this two classes one can do a full webservice call with PHP. The following example is not testet and shows how to work with the PHP SoapClient class. The class is sometimes a bit deceptive and it takes a bit of work to get the right result at the end. But mainly this is the way how to work it.
<?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>";
}
Conclusion
Your web service is very imprecisely defined. For this reason, you should simply rethink the definitions for the parameters and define them more precisely in the WSDL file. Then it works with PHP, too. PHP uses strictly web standards in its soap extension.
So, I have the following data contract:
[DataContract(Namespace = "http://abc/Services/Data")]
public abstract class AbcObject
{
[DataMember]
[XmlAnyElement]
public XmlElement[] Any { get; set; }
}
My expectation is to see the following corresponding xs:complexType element in the generated wsdl:
<xs:complexType name="AbcObject">
<xs:sequence>
<xsd:any minOccurs="0"/>
</xs:sequence>
</xs:complexType>
However, what I actually see is:
<xs:complexType name="AbcObject">
<xs:sequence>
<xs:element name="Any" type="q1:ArrayOfXmlElement" nillable="true" minOccurs="0" xmlns:q1="http://schemas.datacontract.org/2004/07/System.Xml"/>
</xs:sequence>
</xs:complexType>
So, this is not what I want, but I am kind of puzzled how to get the xsd:any generated for me.
Any ideas?
EDIT 1
As per jamiemeyer advice I have changed the DFObject.Any property to be of type XmlAttribute[]. Here is the result:
<xs:complexType name="DFObject">
<xs:sequence>
<xs:element name="Any" type="q1:ArrayOfArrayOfanyType" nillable="true" minOccurs="0" xmlns:q1="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>
</xs:sequence>
</xs:complexType>
Although the result is different, it is still not xsd:any.
This is an example of an XSD for one of the templates that will be stored in the database. The "to_email", "first_name" etc are all tokens and I need to dynamically create a dictionary or an object that can be returned to the client that will give a list of these tokens. The idea behind this is if there is any change in a template then we will just insert another value in the database and it should automatically be created dynamically for returning it to client when they will query for this template.
How do I go about creating/parsing this?
I don't want create a class object with individual element as that would mean that I will have to change it for every addition of the elements. So it has to be something generic.
We should be able to return the set as JSON or XML based on what the client asks for.
How do I go about doing this?
Any help would be appreciated.
Thank you in advance.
<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://tempuri.org/XMLSchema.xsd"
elementFormDefault="qualified" xmlns="http://tempuri.org/XMLSchema.xsd"
xmlns:mstns="http://tempuri.org/XMLSchema.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="claim">
<xs:complexType>
<xs:sequence>
<xs:element name="to_email" type="xs:string"/>
<xs:element name="first_name" type="xs:string"/>
<xs:element name="last_name" type="xs:string"/>
<xs:element name="url" type="xs:string"/>
<xs:element name="received_date" type="xs:date"/>
<xs:element name="contact_number" type="xs:string"/>
<xs:element name="employer_name" type="xs:string"/>
<xs:element name="er_label" type="xs:string"/>
<xs:element name="er_flag" type="xs:integer"/>
<xs:element name="benefit_id" type="xs:integer"/>
<xs:element name="employee_id" type="xs:integer"/>
<xs:element name="employer_id" type="xs:integer"/>
<xs:element name="form_id" type="xs:integer"/>
<xs:element name="er_url" type="xs:string"/>
<xs:element name="tax" type="xs:string"/>
<xs:element name="Repeater" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="form_id" type="xs:integer"/>
<xs:element name="amount" type="xs:integer"/>
<xs:element name="expense_name" type="xs:string"/>
<xs:element name="date_of_service" type="xs:string"/>
<xs:element name="status_of_claim" type="xs:string"/>
<xs:element name="status_reason"/>
<xs:element name="Order_by" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Adding further comments:
The response to the client should be something like each property of the object representing a field. For example a object.
{
‘field1’: <FieldDefinition>
‘field2’: <FieldDefinition>
‘field3’: <FieldDefinition>
.
.
.
}
The object would contains the following properties like Type for example if it is a string or int, Display name i.e. the internal name could be first_name but the display name would FirstName# etc.
Also the could be an array as well. So this makes it little more complex.
Am I making any sense?
I believe you can do the XML processing or you can find that in Google. Plenty of examples are there. I just added the code for logic you have mentioned for the properties. //Your XML processing
var fields = new List<FieldDefinition>
{
new FieldDefinition{ Type="string", DisplayName="FirstName"},
new FieldDefinition{ Type="int", DisplayName="EmployeeId"}
};
var infr = new List<Infrastructure>
{
new Infrastructure { def1=fields.FirstOrDefault()}// loop through to assign each item
};
foreach (var item in infr)
Console.WriteLine(item.def1.DisplayName + " -" + item.def1.Type);
Console.Read();
}
}
public class Infrastructure
{
public FieldDefinition def1 { get; set; }
}
public class FieldDefinition
{
public string Type { get; set; }
public string DisplayName { get; set; }
}
You generate classes for them using the xsd.exe util
Seen here:
How to generate .NET 4.0 classes from xsd?
xsd your.xsd /classes
You can parse your template using XPath query or any generic XML processing. For any template change, you can opt for CacheDependency for File. You just cache the whole xml and if any changes to the tag/file then you insert that portion to the database and reload the cache. You can try SQLDependency also to get similar work.
Another option, you can go for filesystemwatcher which will raise event for changing in the file content. Then you just need to insert the added portion in the database.
Is there any way to deserialize elements containing either simple text or subelement to a string with XmlSerializer?
Xml sample:
<Attribute>
<AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">thisistext</AttributeValue>
<AttributeValue>
<e:Authorities xmlns:e="urn:dummy">
<e:Authority>ORG_CHIEF</esia-encoder:Authority>
</e:Authorities>
</AttributeValue>
</Attribute>
C# property:
[XmlElement("AttributeValue", IsNullable = true)]
public string[] AttributeValue { get; set; }
Deserialization of the first AttributeValue succeed, but the next one fails. No wonder, beacause ReadElementString method expects simple or empty content. I'm looking for a way to tell to serializer "put content of this element to a string, whatever it contains".
Actually, the 2nd value you have in your XML is:
<e:Authorities xmlns:e="urn:dummy">
<e:Authority>ORG_CHIEF</esia-encoder:Authority>
</e:Authorities>
This is no valid string dataType which is expected because of:
public string[] AttributeValue {get; set;}
If you are able to define it in XSD, you can use XSD2Code or xsd.exe to create a class for the XSD to deserialize into.
What about this one?
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="Attribute">
<xs:complexType>
<xs:sequence>
<xs:element name="AttributeValue" maxOccurs="unbounded">
<xs:complexType>
<xs:choice>
<xs:element name="AuthoritiesString" type="xs:string"/>
<xs:element name="AuthoritiesElement">
<xs:complexType>
<xs:sequence>
<xs:element name="Authority"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
I have a WebService that returns complex objects.
public class Resource;
public class Asset extends Resource;
public class Collection extends Resource;
The methods that return the subclasses work fine:
Asset[] getAssets();
Collection[] getCollections();
However the methods that return the base class are generating an exception:
Resource[] getResources();
The WebService itself is written in java using CXF but I dont think that matters. I am able to use even the base class methods with a sample client created with java.
Here is the exception using .Net Client:
`Unhandled Exception: System.InvalidOperationException: There is an error in XML document (1, 173). ---> System.InvalidOperationException: The specified type was not recognized: name='asset', namespace='http://www.turimsaint.com/', at <return xmlns=''>`.
Here is the client Code Generated by wsdl.exe
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("", RequestNamespace = "http://www.turimsaint.com/", ResponseNamespace = "http://www.turimsaint.com/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[return: System.Xml.Serialization.XmlElementAttribute("return", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public Resource[] getCollectionContents([System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] Collection p_collection)
{
object[] results = this.Invoke("getCollectionContents", new object[] {
p_collection});
return ((Resource[])(results[0]));
}
And lastly this is the wsdl for the related elements:
- <xs:complexType name="collection">
- <xs:complexContent>
- <xs:extension base="tns:resource">
- <xs:sequence>
<xs:element name="_instructor" type="xs:string" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
- <xs:complexType abstract="true" name="resource">
- <xs:sequence>
<xs:element name="_title" type="xs:string" />
<xs:element name="_deepLink" type="xs:string" />
<xs:element name="_id" type="xs:string" />
<xs:element name="_description" type="xs:string" />
<xs:element name="_type" type="xs:string" />
<xs:element maxOccurs="unbounded" name="_metadata" type="tns:resourceMetadata" />
</xs:sequence>
</xs:complexType>
....
- <xs:complexType name="getResourceByIDResponse">
- <xs:sequence>
<xs:element minOccurs="0" name="return" type="tns:resource" />
</xs:sequence>
</xs:complexType>
Any help appreciated.
Thanks.
This is digging a little, its been a while since playing with .Net 2.0 style web service clients.
If i remember correctly, you´ll have to do some attribute work. You´ll have to inform the base class ´resource´ about the possibly derived classes ´Asset´-´Collection´ or methods returning one of the derived classes by placing attributes on top.
Have a look at
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlincludeattribute.aspx
Hope this helps,