Deserializing an XML array from a web api - c#

First off, I followed the answer given here, but I still can not get the following to work.
I am retrieving XML from a web API, and the results returned are as such:
<ArrayOf__ptd_student_charges
xmlns="http://schemas.datacontract.org/2004/07/something.something"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<__ptd_student_charges>
<accumulated_tuition>000.000</accumulated_tuition>
<course_id>AAA-000/L</course_id>
<invoice_date>01/01/2015</invoice_date>
<lecturer_name>John Doe</lecturer_name>
<net_tuition>000.000</net_tuition>
<section_no>1</section_no>
<semester>Summer</semester>
<student_id>123456</student_id>
<student_name>John Doe</student_name>
<year>2015</year>
</__ptd_student_charges>
<__ptd_student_charges>
<accumulated_tuition>000.000</accumulated_tuition>
<course_id>AAA-000/L</course_id>
<invoice_date>01/01/2015</invoice_date>
<lecturer_name>John Doe</lecturer_name>
<net_tuition>000.000</net_tuition>
<section_no>1</section_no>
<semester>Summer</semester>
<student_id>123456</student_id>
<student_name>John Doe</student_name>
<year>2015</year>
</__ptd_student_charges>
</ArrayOf__ptd_student_charges>
I'm trying to deserialize this into an array of students.
My student class is defined like this:
public class Student
{
[System.Xml.Serialization.XmlElement("accumulated_tuiton")]
public double AccumulatedTution { get; set; }
[System.Xml.Serialization.XmlElement("net_tuiton")]
public double NetTuiton { get; set; }
[System.Xml.Serialization.XmlElement("course_id")]
public string CourseID { get; set; }
[System.Xml.Serialization.XmlElement("invoice_date")]
public DateTime InvoiceDate { get; set; }
[System.Xml.Serialization.XmlElement("lecturer_name")]
public string LecturerName { get; set; }
[System.Xml.Serialization.XmlElement("semester")]
public string Semester { get; set; }
[System.Xml.Serialization.XmlElement("student_id")]
public string StudentId { get; set; }
[System.Xml.Serialization.XmlElement("student_name")]
public string StudentName { get; set; }
[System.Xml.Serialization.XmlElement("year")]
public string Year { get; set; }
[System.Xml.Serialization.XmlElement("section_no")]
public int Section { get; set; }
}
And my student collection is defined like this:
[System.Xml.Serialization.XmlRoot("ArrayOf__ptd_student_charges xmlns=\"http://schemas.datacontract.org/2004/07/something.something\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance")]
public class StudentCollection
{
[XmlArray("ArrayOf__ptd_student_charges")]
[XmlArrayItem("__ptd_student_charges", typeof(Student))]
public Student[] StudentArray { get; set; }
}
I'm deserializing the results using this code:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
StudentCollection collection;
HttpWebRequest request = WebRequest.Create(stringUrl) as HttpWebRequest;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
XmlTextReader reader = new XmlTextReader(response.GetResponseStream());
XmlSerializer serializer = new XmlSerializer(typeof(StudentCollection));
collection = (StudentCollection)serializer.Deserialize(reader);
reader.Close();
}
Once I run this, I get an InvalidOperationException with a message
ArrayOf__ptd_student_charges
xmlns='http://schemas.datacontract.org/2004/07/something.something'>
was not expected.
I know that the xmlns:... shouldn't be in the first tag, but unfortunately it is and I'm unsure on how to proceed.

Basically, you need to support the default XML namespace in your XML file - you can either do this by specifying it on the StudentCollection:
[System.Xml.Serialization.XmlRoot("ArrayOf__ptd_student_charges")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.datacontract.org/2004/07/something.something", IsNullable = false)]
public class StudentCollection
{
[XmlArray("ArrayOf__ptd_student_charges")]
[XmlArrayItem("__ptd_student_charges", typeof(Student))]
public Student[] StudentArray { get; set; }
}
and the actual Student class:
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.datacontract.org/2004/07/something.something", IsNullable = false)]
public class Student
{
..........
}
or you can specify it programmatically when you deserialize:
XmlSerializer serializer = new XmlSerializer(typeof(StudentCollection),
"http://schemas.datacontract.org/2004/07/something.something");
That second parameter for the XmlSerializer is the default XML namespace to use when deserializing the XML content.
Extra tipp: if you ever have an XML file again, and you need to get the C# code classes that represent that XML - if you have Visual Studio 2012 or newer, just create a new code class, copy your XML file into the clipboard, and then use Edit > Paste Special > Paste XML as classes and you get all your C# including all XML attribute and XML namespaces and everything pasted into your Visual Studio right there

Related

List Property Not Being Populated on Deserialisation from XML

I am trying to deserialise an xml file to c# classes. I used an online tool to generate the classes for me as the structure of the XML file is quite complex. This worked well except for the population of repeated items into a List property in the main class.
I am using DotNet 4.5, C# in WPF.
A simplified version of the xml file is as follows:
<orderMessage xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:gs1:ecom:order:xsd:3">
<order xmlns="">
<creationDateTime>2017-07-10T00:00:00</creationDateTime>
<documentStatusCode>ORIGINAL</documentStatusCode>
<documentActionCode>ADD</documentActionCode>
</order>
<order xmlns="">
<creationDateTime>2017-07-10T00:00:00</creationDateTime>
<documentStatusCode>ORIGINAL</documentStatusCode>
<documentActionCode>ADD</documentActionCode>
</order>
</orderMessage>
The classes that I am using are as below:
[XmlRoot(ElementName = "order")]
public class Order
{
[XmlElement(ElementName = "creationDateTime")]
public string CreationDateTime { get; set; }
[XmlElement(ElementName = "documentStatusCode")]
public string DocumentStatusCode { get; set; }
[XmlElement(ElementName = "documentActionCode")]
public string DocumentActionCode { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
}
[XmlRoot(ElementName = "orderMessage", Namespace = "urn:gs1:ecom:order:xsd:3")]
public class OrderMessage
{
[XmlElement(ElementName = "order")]
public List<Order> Order { get; set; }
[XmlAttribute(AttributeName = "xsd", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsd { get; set; }
[XmlAttribute(AttributeName = "xsi", Namespace = "http://www.w3.org/2000/xmlns/")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
}
The deserialization code is as follows:
XmlSerializer serializer = new XmlSerializer(typeof(OrderMessage));
StreamReader reader = new StreamReader(filename);
OrderMessage newOrderMessage;
try
{
newOrderMessage = (OrderMessage)serializer.Deserialize(reader);
}
catch (Exception e)
{
throw e;
}
reader.Close();
When I run the code it runs without error but I end up with an empty list. There were other structures in the xml (that were not repeated structures - therefore no list property) that populated without problem that I have omitted.
I have looked at a number of questions similar to mine but they seem to suggest the same method I am using.
I am unable to change the XML as it is from a third party.
I would greatly appreciate it if anyone could point me in the right direction.
BTW - I know that catching an error and then throwing it is of no use whatsoever but I just did that to add a breakpoint so I could look at the inner exceptions if there were any. I will make the error handling more meaningful once I have the process working.
The issue is your Order property - the namespace will be inherited from OrderMessage, so it is urn:gs1:ecom:order:xsd:3 when it should be empty. You must specify this explicitly.
You can also remove a bunch of the namespace related attributes from your model. This is all you need:
[XmlRoot("orderMessage", Namespace = "urn:gs1:ecom:order:xsd:3")]
public class OrderMessage
{
[XmlElement("order", Namespace = "")]
public List<Order> Orders { get; set; }
}
public class Order
{
[XmlElement("creationDateTime")]
public string CreationDateTime { get; set; }
[XmlElement("documentStatusCode")]
public string DocumentStatusCode { get; set; }
[XmlElement("documentActionCode")]
public string DocumentActionCode { get; set; }
}
As an aside, throw e; in your deserialisation code is probably not what you want to do (see this question). Given you're not actually handling the exception, you can remove the try / catch entirely in this case.
You should also enclose your StreamReader in a using block to ensure it is disposed after use.

XML Deserialization error: xxxxx was not expected

I know there are several posts out there with this topic, but I can't seem to figure out what is the problem here. I have serialized and deserialized xml several times, and never had this error.
The exception message is:
There is an error in XML document (1, 2).
With InnerException:
<InvoiceChangeRequest xmlns=''> was not expected.
XML file I want to deserialize:
<ns1:InvoiceChangeRequest xmlns:ns1="http://kmd.dk/fie/external_invoiceDistribution">
<CONTROL_FIELDS>
<STRUCTURID>0000000001</STRUCTURID>
<OPERA>GET</OPERA>
<WIID>000050371220</WIID>
</CONTROL_FIELDS>
<HEADER_IN>
<MANDT>751</MANDT>
<BELNR>1234567890</BELNR>
</HEADER_IN>
<ITEMS>
<ITEM_FIELDS_IN>
<BUZEI>001</BUZEI>
<BUKRS>0020</BUKRS>
</ITEM_FIELDS_IN>
</ITEMS>
</ns1:InvoiceChangeRequest>
Class I'm trying to deserialize to:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")] public ControlFields Styrefelter;
[XmlElement("HEADER_IN")] public HeaderIn HeaderfelterInd;
[XmlElement("ITEMS")] public Items Linjer;
}
public class HeaderIn
{
[XmlElement("MANDT")] public string Kommunenummer;
[XmlElement("BELNR")] public string RegnskabsbilagsNummer;
}
public class Items
{
[XmlElement("ITEM_FIELDS_IN")] public Itemfield[] ItemfelterInd;
}
public class Itemfield
{
[XmlElement("BUZEI")] public string Linjenummer;
[XmlElement("BUKRS")] public string Firmakode;
}
Deserialization code:
XmlSerializer serializer = new XmlSerializer(typeof(InvoiceChangeRequest));
var request = serializer.Deserialize(new StringReader(output)) as InvoiceChangeRequest;
In your XML file your root element is the namespace http://kmd.dk/fie/external_invoiceDistribution with prefix ns1.
The element <CONTROL_FIELDS> isn't because it isn't prefixed. Your serialization class doesn't take this into account though. That means that it expects that <CONTROL_FIELDS> and the other elements are ALSO in the ns1 namespace.
To get the serializer parse the elements correctly add the Namespace to the elements, setting it to an empty string:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS", Namespace = "")]
public ControlFields Styrefelter { get; set; }
[XmlElement("HEADER_IN", Namespace = "")]
public HeaderIn HeaderfelterInd { get; set; }
[XmlElement("ITEMS", Namespace = "")]
public Items Linjer { get; set; }
}
This will de-serialize the given XML as intended.
In case of de-serialization issues I often create the classes in memory and then serialize that so I can inspect the resulting XML. That often gives clues on what is missing or being added compared to the input document:
var ms = new MemoryStream();
serializer.Serialize(ms, new InvoiceChangeRequest {
Styrefelter = new ControlFields { Opera="test"}
});
var s = Encoding.UTF8.GetString(ms.ToArray());
And then inspect s for differences.
You can replace 'ns1:' with string.Empty.
Below classes should serialize.
public class Item
{
[XmlElement("BUZEI")]
public string Buzei { get; set; }
[XmlElement("BUKRS")]
public string Bukrs { get; set; }
}
public class Header
{
[XmlElement("MANDT")]
public string Mandt { get; set; }
[XmlElement("BELNR")]
public string Belnr { get; set; }
}
public class ControlFields
{
[XmlElement("STRUCTURID")]
public string StructuredId { get; set; }
[XmlElement("OPERA")]
public string Opera { get; set; }
[XmlElement("WIID")]
public string Wild { get; set; }
}
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")]
public ControlFields ControlFields { get; set; }
[XmlElement("HEADER_IN")]
public Header Header { get; set; }
[XmlArray("ITEMS")]
[XmlArrayItem("ITEM_FIELDS_IN")]
public List<Item> Items { get; set; }
}

Alter XML root name [duplicate]

First off, I followed the answer given here, but I still can not get the following to work.
I am retrieving XML from a web API, and the results returned are as such:
<ArrayOf__ptd_student_charges
xmlns="http://schemas.datacontract.org/2004/07/something.something"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<__ptd_student_charges>
<accumulated_tuition>000.000</accumulated_tuition>
<course_id>AAA-000/L</course_id>
<invoice_date>01/01/2015</invoice_date>
<lecturer_name>John Doe</lecturer_name>
<net_tuition>000.000</net_tuition>
<section_no>1</section_no>
<semester>Summer</semester>
<student_id>123456</student_id>
<student_name>John Doe</student_name>
<year>2015</year>
</__ptd_student_charges>
<__ptd_student_charges>
<accumulated_tuition>000.000</accumulated_tuition>
<course_id>AAA-000/L</course_id>
<invoice_date>01/01/2015</invoice_date>
<lecturer_name>John Doe</lecturer_name>
<net_tuition>000.000</net_tuition>
<section_no>1</section_no>
<semester>Summer</semester>
<student_id>123456</student_id>
<student_name>John Doe</student_name>
<year>2015</year>
</__ptd_student_charges>
</ArrayOf__ptd_student_charges>
I'm trying to deserialize this into an array of students.
My student class is defined like this:
public class Student
{
[System.Xml.Serialization.XmlElement("accumulated_tuiton")]
public double AccumulatedTution { get; set; }
[System.Xml.Serialization.XmlElement("net_tuiton")]
public double NetTuiton { get; set; }
[System.Xml.Serialization.XmlElement("course_id")]
public string CourseID { get; set; }
[System.Xml.Serialization.XmlElement("invoice_date")]
public DateTime InvoiceDate { get; set; }
[System.Xml.Serialization.XmlElement("lecturer_name")]
public string LecturerName { get; set; }
[System.Xml.Serialization.XmlElement("semester")]
public string Semester { get; set; }
[System.Xml.Serialization.XmlElement("student_id")]
public string StudentId { get; set; }
[System.Xml.Serialization.XmlElement("student_name")]
public string StudentName { get; set; }
[System.Xml.Serialization.XmlElement("year")]
public string Year { get; set; }
[System.Xml.Serialization.XmlElement("section_no")]
public int Section { get; set; }
}
And my student collection is defined like this:
[System.Xml.Serialization.XmlRoot("ArrayOf__ptd_student_charges xmlns=\"http://schemas.datacontract.org/2004/07/something.something\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance")]
public class StudentCollection
{
[XmlArray("ArrayOf__ptd_student_charges")]
[XmlArrayItem("__ptd_student_charges", typeof(Student))]
public Student[] StudentArray { get; set; }
}
I'm deserializing the results using this code:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
StudentCollection collection;
HttpWebRequest request = WebRequest.Create(stringUrl) as HttpWebRequest;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
XmlTextReader reader = new XmlTextReader(response.GetResponseStream());
XmlSerializer serializer = new XmlSerializer(typeof(StudentCollection));
collection = (StudentCollection)serializer.Deserialize(reader);
reader.Close();
}
Once I run this, I get an InvalidOperationException with a message
ArrayOf__ptd_student_charges
xmlns='http://schemas.datacontract.org/2004/07/something.something'>
was not expected.
I know that the xmlns:... shouldn't be in the first tag, but unfortunately it is and I'm unsure on how to proceed.
Basically, you need to support the default XML namespace in your XML file - you can either do this by specifying it on the StudentCollection:
[System.Xml.Serialization.XmlRoot("ArrayOf__ptd_student_charges")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.datacontract.org/2004/07/something.something", IsNullable = false)]
public class StudentCollection
{
[XmlArray("ArrayOf__ptd_student_charges")]
[XmlArrayItem("__ptd_student_charges", typeof(Student))]
public Student[] StudentArray { get; set; }
}
and the actual Student class:
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.datacontract.org/2004/07/something.something", IsNullable = false)]
public class Student
{
..........
}
or you can specify it programmatically when you deserialize:
XmlSerializer serializer = new XmlSerializer(typeof(StudentCollection),
"http://schemas.datacontract.org/2004/07/something.something");
That second parameter for the XmlSerializer is the default XML namespace to use when deserializing the XML content.
Extra tipp: if you ever have an XML file again, and you need to get the C# code classes that represent that XML - if you have Visual Studio 2012 or newer, just create a new code class, copy your XML file into the clipboard, and then use Edit > Paste Special > Paste XML as classes and you get all your C# including all XML attribute and XML namespaces and everything pasted into your Visual Studio right there

Using 'Paste XML as Classes' to deserialize a Web API rest response

I am having trouble using the 'Paste XML as Classes' feature in VS2012 to properly deserialize XML results from a Rest call using Web API.
The XML response from the call looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SCResponse>
<AccountId>86</AccountId>
<Administrator>false</Administrator>
<Email>6z#z.com</Email>
<FirstName>6z#z.com</FirstName>
<Label>false</Label>
<LastName>6z#z.com</LastName>
<link href="https://cnn.com" rel="news" title="News"/>
</SCResponse>
I copied this XML and used the handy new feature to paste this XML as classes:
namespace Models.account.response
{
[XmlRoot(ElementName = "SCResponse")] // I added this so I could name the object Account
[DataContract(Name = "SCResponse", Namespace = "")] // I added this as the namespace was causing me problems
public partial class Account
{
public byte AccountId { get; set; }
public bool Administrator { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public bool Label { get; set; }
public string LastName { get; set; }
[XmlElement("link")]
public SCResponseLink[] Link { get; set; }
}
[XmlType(AnonymousType = true)]
public partial class SCResponseLink
{
private string hrefField;
private string relField;
private string titleField;
[XmlAttribute)]
public string href { get; set; }
XmlAttribute]
public string rel { get; set; }
[XmlAttribute]
public string title { get; set; }
}
}
}
I call the REST endpoint like so:
string path = String.Format("account/{0}", id);
HttpResponseMessage response = client.GetAsync(path).Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
account = response.Content.ReadAsAsync<Models.account.response.Account>().Result;
}
and examine the fields of the Account object -- all are null or defaulting to initialized values.
In my Global.asax.cs Application_Start method, I am registering the XML Serializer:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
A simpler way to handle this might be to use the RestSharp library, which will do all of the deserialization for you. This will simplify your calls, and you won't need the XML attributes on your model.
Take a look here for a good example of doing aync calls with RestSharp:
How should I implement ExecuteAsync with RestSharp on Windows Phone 7?
Hopefully this helps.

Deserialize xml element that is empty but has attributes

I have some XML with an element like this:
<hour base_forecast="12" datim="29/0" />
And am receiving the error:
Unexpected node type Element. ReadElementString method can only be
called on elements with simple or empty content.
I am guessing this is because the element has no value. I don't control this XML so I can't change it. How would I deserialize this?
** EDIT **
One of the attributes' values is ">6" .... could this be the culprit? If so, how do I handle that?
** Update **
Found some data that wasn't returning a > in a value of the attribute. Same error is occurring.
** Edit #3 *
Created an XSD for the XML I am receiving, then generated classes for them with the xsd tool. Adding to the bottom of this post.
Here is the Deserialization code:
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("xxx");
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
WeatherData Result = new WeatherData();
using (Stream st = resp.GetResponseStream())
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "model_data";
xRoot.IsNullable = true;
Result = new XmlSerializer(typeof(WeatherData), xRoot).Deserialize(st) as WeatherData; ** Error here
Xml returned:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE observation SYSTEM "http://private.com/hithere.dtd">
<model_data>
<site a="28/12" b="KXXX">
<hour x="-9999" y="-9999" z="-9999"/>
</site>
</model_data>
Data object
[Serializable, XmlRoot("model_data")]
public class WeatherData
{
[XmlElement("site")]
public string City { get; set; }
[XmlAttribute]
public string a { get; set; }
[XmlAttribute]
public string b { get; set; }
[XmlElement(ElementName="hour", IsNullable=true)]
public string Hour { get; set; }
[XmlAttribute]
public string x { get; set; }
[XmlAttribute]
public string y { get; set; }
[XmlAttribute]
public string z { get; set; }
}
XSD Tool generated classes
**Removed generated classes, but they are similar to what Hugo posted **
Looking at this part:
[XmlElement("site")]
public string City { get; set; }
<Site> contains <Hour>, so it is not an element with simple or empty content, I guess?
Edit: actually the whole thing seems suspect.
The data object seems to disregard all hierarchy information in the xml.
How about something like this?
[Serializable, XmlRoot("model_data")]
public class WeatherData
{
[XmlElement("site")]
public City[] City { get; set; }
}
public class City
{
[XmlAttribute]
public string a { get; set; }
[XmlAttribute]
public string b { get; set; }
[XmlElement(ElementName="hour", IsNullable=true)]
public Hour Hour { get; set; }
}
public class Hour
{
[XmlAttribute]
public string x { get; set; }
[XmlAttribute]
public string y { get; set; }
[XmlAttribute]
public string z { get; set; }
}
When I generated the classes with the XSD tool, I getting the same error, but the error was showing being thrown from a line I had commented out.
So I exited VS, and ran disk cleanup. Ran my code again. Received a message "For security reasons DTD is prohibited in this XML document. etc." So, I allowed set the reader to DtdProcessing.Parse, and ran the code once more.
It was successful.

Categories

Resources