How to parse xml with multiple xmlns attribute in c#? - c#

I have an xml beginning like above
<Invoice
xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2../xsdrt/maindoc/UBL-Invoice-2.1.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
<cbc:CustomizationID>TR1.2</cbc:CustomizationID>
<cbc:ProfileID>TEMELFATURA</cbc:ProfileID>
<cbc:ID>ALP2018000007216</cbc:ID>
<!-- ... -->
and I try to parse the xml with method like that
public static T FromXml<T>(string xmlString)
{
StringReader xmlReader = new StringReader(xmlString);
XmlSerializer deserializer = new XmlSerializer(typeof(T));
return (T)deserializer.Deserialize(xmlReader);
}
and my xml model is like above
[Serializable]
[XmlRoot(
Namespace = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
ElementName = "Invoice",
DataType = "string",
IsNullable = true)]
public class Invoice
{
public string CustomizationID { get; set; }
// ...
}
However, I cannot parse the xml document, all values come null. I think that it is because of multiple xmlns attribute in Invoice tag. I couldnt solve the problem.

The default namespace of the document is urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 which you have correctly put in the XmlRoot, but the child elements such as UBLVersionID are prefixed with cbc, which is a different namespace. You have to put that namespace against the property to let the serializer know that's what it is.
For example:
[Serializable]
[XmlRoot(
Namespace = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
ElementName = "Invoice",
DataType = "string",
IsNullable = true)]
public class Invoice
{
[XmlElement(Namespace = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2")]
public string CustomizationID { get; set; }
// ...
}
In Visual Studio, you can use Edit > Paste Special > Paste Xml As Classes to see how to decorate a class to match your XML if you're in doubt.

Related

Deserialize part of SOAP response

I am a bit lost with the deserialization of a particular part of my soap response.
The response:
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body>
<ns1:LoginResult xmlns:ns1="http://abc.def.schema">
<sessionId>123456789</sessionId>
<sessionTimeout>30</sessionTimeout>
<organizationName>WebService Test Account XYZ</organizationName>
<userInfoResult>
<accessibleOrgs>
<name>WebService Test Account XYZ</name>
<description/>
<prefix>10</prefix>
<countryCallingCode>+49</countryCallingCode>
<treeLevel>0</treeLevel>
<timeZone>
<timeZoneId>Europe/Berlin</timeZoneId>
<currentUtcOffset>3600000</currentUtcOffset>
</timeZone>
<billingCompany>COMPANY 123</billingCompany>
<language>DE</language>
</accessibleOrgs>
<isDemo>true</isDemo>
<prefixLength>0</prefixLength>
<alarmNumberLength>0</alarmNumberLength>
<groupNumberLength>0</groupNumberLength>
<personNumberLength>0</personNumberLength>
</userInfoResult>
</ns1:LoginResult>
</soapenv:Body>
I need to deserialize the "LoginResult" part. I am aware of deserialization methods but I am struggling with the fact that A) there are namespaces and B) I just need a subset of the XML.
Maybe somebody could point me into the right direction.
Thx in advcance
Start with definition of LoginResult class.
[XmlRootAttribute(Namespace = "http://abc.def.schema", IsNullable = false, ElementName = "LoginResult")]
public class LoginResult
{
[XmlElement(Namespace ="")]
public int sessionId { get; set; }
[XmlElement(Namespace = "")]
public string organizationName { get; set; }
..... some more properties
}
Use XDocument class from System.Xml.Linq to parse the xml.
Find the "LoginResult" element.
Deserialize as LoginResult type.
var xDoc = XDocument.Parse(str);
var xLoginResult = xDoc.Root.Descendants().FirstOrDefault(d => d.Name.LocalName.Equals("LoginResult"));
var serializer = new XmlSerializer(typeof(LoginResult));
using (var reader = xLoginResult.CreateReader())
{
var result = (LoginResult)serializer.Deserialize(reader);
}

How to remove empty namespace attribute on manually added xml string when serializing object?

I am using XmlSerializer to output my object model to XML. Everything works very well but now I need to add several lines of pre-built XML to the object without building classes for each line. After lots of searching, I found that I can convert the xml string to an XmlElement using XmlDocument's LoadXml and DocumentElement calls. I get the XML I want except that the string section has an empty namespace. How can I eliminate the empty namespace attribute? Is there a better way to add an xml string to the object and have it be serialized properly?
Note: I am only creating output so I don't need to deserialize the generated XML. I am fairly new to the C#, .NET world, and hence, XmlSerialize.
Here is my code:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public XmlElement Extension { get; set; }
public Book()
{
}
public void AddExtension()
{
string xmlString = "<AdditionalInfo>" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";
this.Extension = GetElement(xmlString);
}
public static XmlElement GetElement(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc.DocumentElement;
}
}
static void Main(string[] args)
{
TestSerialization p = new TestSerialization();
Book bookOne = new Book();
bookOne.Title = "How to Fix Code";
bookOne.Author = "Dee Bugger";
bookOne.AddExtension();
System.Xml.Serialization.XmlSerializer serializer = new XmlSerializer(typeof(Book), "http://www.somenamespace.com");
using (var writer = new StreamWriter("C:\\BookReport.xml"))
using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { Indent = true }))
{
serializer.Serialize(xmlWriter, bookOne);
}
}
Here is my output:
<?xml version="1.0" encoding="utf-8"?>
<Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.somenamespace.com">
<Title>How to Fix Code</Title>
<Author>Dee Bugger</Author>
<Extension>
<AdditionalInfo xmlns="">
<SpecialHandling>Some Value</SpecialHandling>
</AdditionalInfo>
</Extension>
</Book>
It is the xmlns="" on AdditionalInfo that I want to eliminate. I believe this coming out because there is no association between the XmlDocument I created and the root serialized object, so the XmlDocument creates its own namespace. How can I tell the XmlDocument (and really, the generated XmlElement) that it belongs to the same namespace as the serialized object?
This is added because the parent elements have a namespace and your AdditionalInfo element does not. The xmlns="" attribute changes the default namespace for that element and its children.
If you want to get rid of it, then presumably you want the AdditionalInfo element to have the same namespace as its parent. In which case, you need to change your XML to this:
string xmlString = #"<AdditionalInfo xmlns=\"http://www.somenamespace.com\">" +
"<SpecialHandling>Some Value</SpecialHandling>" +
"</AdditionalInfo>";

XML Serializing Element with prefix

<?xml version="1.0" encoding="UTF-8"?>
<rs:model-request xsi:schemaLocation="http://www.ca.com/spectrum/restful/schema/request ../../../xsd/Request.xsd " xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rs="http://www.ca.com/spectrum/restful/schema/request" throttlesize="100">
<rs:target-models>
I am having trouble understanding the C# XmlSerializer. I have successfully been able to serialize elements that do not have a prefix such as rs:* above. I also have no been able to find how to add the xsi:, xmlns:xsi and xmlns:rs (namespaces?).
Would someone be able to create a simple class to show how to generate the above XML?
Fields, properties, and objects can have a namespace associated with them for serialization purposes. You specify the namespaces using attributes such as [XmlRoot(...)], [XmlElement(...)], and [XmlAttribute(...)]:
[XmlRoot(ElementName = "MyRoot", Namespace = MyElement.ElementNamespace)]
public class MyElement
{
public const string ElementNamespace = "http://www.mynamespace.com";
public const string SchemaInstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance";
[XmlAttribute("schemaLocation", Namespace = SchemaInstanceNamespace)]
public string SchemaLocation = "http://www.mynamespace.com/schema.xsd";
public string Content { get; set; }
}
Then you associate the desired namespace prefixes during serialization by using the XmlSerializerNamespaces object:
var obj = new MyElement() { Content = "testing" };
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("xsi", MyElement.SchemaInstanceNamespace);
namespaces.Add("myns", MyElement.ElementNamespace);
var serializer = new XmlSerializer(typeof(MyElement));
using (var writer = File.CreateText("serialized.xml"))
{
serializer.Serialize(writer, obj, namespaces);
}
The final output file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<myns:MyRoot xmlns:myns="http://www.mynamespace.com" xsi:schemaLocation="http://www.mynamespace.com/schema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<myns:Content>testing</myns:Content>
</myns:MyRoot>

XML Element Selection

I am collecting XML data from a weather website xml file here.
I have created the following code, which finds the first instance of 'temp_c' and returns the value. This is the temperature at the current moment.
using (XmlReader reader = XmlReader.Create(new StringReader(weather)))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name.Equals("temp_c"))
{
reader.Read();
temp_c = reader.Value;
}
break;
}
}
}
return temp_c
This returns the value of the first instance in the XML file called "temp_c" to the string called "temp_c"
What I would now like to do is use the Element in the XML document called "period" and the element found with period called "fcttext". When "period = 0" it means "today", 1 = tomorrow, etc; and I am after the "fcttext_metric" data that is associated with that period value.
I've been struggling to work the examples and XML reading codes I've found into the current code that I have here. Can anyone please point me in the right direction?
Edit
Here is an example of the XML file:
<forecast>
<forecastday>
<period>0</period>
<fcttext_metric>Sunny</fcttext_metric>
<forecastday>
<period>1</period>
<fcttext_metric>Cloudy</fcttext_metric>
This ended up being more annoying that I originally expected, but you can create an object graph from the XML using a DataContractSerializer and a set of classes that match the XML you are reading.
First, you create your classes, with appropriate DataContract attributes.
[DataContract(Name = "response", Namespace = "")]
public class WeatherData
{
[DataMember(Name = "forecast")]
public Forecast Forecast { get; set; }
}
[DataContract(Name = "forecast", Namespace = "")]
public class Forecast
{
[DataMember(Name = "txt_forecast")]
public TxtForecast TxtForecast { get; set; }
}
[DataContract(Name = "txt_forecast", Namespace = "")]
public class TxtForecast
{
[DataMember(Name = "forecastdays")]
public ForecastDay[] ForecastDays { get; set; }
}
[DataContract(Name = "forecastday", Namespace = "")]
public class ForecastDay
{
[DataMember(Name = "period", Order = 1)]
public int Period { get; set; }
public string FctText { get; set; }
[DataMember(Name = "fcttext", EmitDefaultValue = false, Order = 5)]
private CDataWrapper FctTextWrapper
{
get { return FctText; }
set { FctText = value; }
}
}
Heres where it got complicated. The fcttext element is CDATA, which the DataContractSerializer doesn't support by default.
Using the wonderful answer available at Using CDATA with WCF REST starter kits, you create a CDataWrapper class. I won't repeat the code here (because that would be pointless), just follow the link.
You can see that I've already used the CDataWrapper class above in order to work with the fcttext element.
Once you've got the class structure setup you can use the following code to extract the information you were after. You're just navigating the object graph at this point, so you can use whatever C# you want (I've used a simple LINQ query to find Period 0 and print out the fcttext for it).
var s = new DataContractSerializer(typeof(WeatherData));
var reader = XmlReader.Create("http://api.wunderground.com/api/74e1025b64f874f6/forecast/conditions/q/zmw:00000.1.94787.xml");
var o = (WeatherData)s.ReadObject(reader);
var firstPeriod = o.Forecast.TxtForecast.ForecastDays.Where(a => a.Period == 0).Single();
Console.WriteLine(firstPeriod.FctText);
You can extend the classes as necessary to give you access to additional fields inside the XML. As long as the DataMember names match the XML fields it will all work.
Here's a short summary of some problems I ran into for anyone interested:
I had to set the Namespace attributes of all of the classes to the empty string, because the XML doesn't have any namespace info in it.
The Order attributes were important on the ForecastDay class. If they are omitted, the DataContractSerializer ends up not reading the fcttext field in at all (because it thinks fcttext should come first? not sure why to be honest).

XML namespace in ASP.net MVC, C#

I'm trying to get an XML file generated using a namespace as such:
<namespace:Example1>
<namespace:Part1>Value1</namespace:Part1>
</namespace:Example1>
I've tried using
[XmlAttribute(Namespace = "namespace")]
public string Namespace { get; set; }
but I'm clearly missing something. The structure I've used is
[XmlRoot("Example1")]
public class Blah
{
[XmlAttribute(Namespace = "namespace")]
public string Namespace { get; set; }
but all I get is
<Example1>
<Part1>Value1</Part1>
</Example1>
Any help would be greatly appreciated.
Edit:
[XmlRoot(ElementName="Chart2", Namespace="vc")]
doesn't work.
You can use the XmlSerializerNamespaces class to add the prefix for a given namespace in the xml.
I hope the below code will he you better.
[XmlRoot(ElementName = "Example1")]
public class Blah
{
public string Part1 { get; set; }
}
Blah bl = new Blah();
bl.Part1 = "MyPart1";
// Serialization
/* Create an XmlSerializerNamespaces object and add two prefix-namespace pairs. */
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("namespace", "test");
XmlSerializer s = new XmlSerializer(typeof(Blah),"test");
TextWriter w = new StreamWriter(#"c:\list.xml");
s.Serialize(w, bl,ns);
w.Close();
/* Output */
<?xml version="1.0" encoding="utf-8"?>
<namespace:Example1 xmlns:namespace="test">
<namespace:Part1>MyPart1</namespace:Part1>
</namespace:Example1>
Can you try this on your Model.cs:
Copy the whole XML, then on the Model.cs:
Edit > Paste Special > Paste XML as Classes.
Might help you. ;)

Categories