This is my current XML Response tag
<Response_Data_1234 diffgr:id="Response_Data_1234" msdata:rowOrder="0" diffgr:hasChanges="inserted">
<Status>File received.</Status>
<Time>2022-01-25T09:44:15.73+08:00</Time>
<Location>HKG</Location>
</Response_Data_1234>
the number 1234 in <Response_Data_1234> is a Response Data id, which will be dynamic depending on the request.
Could someone please me create a C# class in this scenario so that I can map the response directly to the class. Thanks in advance
Actually you should not have any unique identifier attached to XML element name. It should be added as attribute.
To answer your question:
There is no direct class attribute to ignore class name while deserialisation of xml string to object.
Option1 (better): Create mapper extension method for XNode to class attribute mapping and reuse it.
Option2:
If your xml string is small you can try to update name of root element and use it to deserialise.
sample code:
var responseDataXDoc = XDocument.Parse(xml);
responseDataXDoc.Root.Name = "ResponseData";
var serializer = new XmlSerializer(typeof(ResponseData));
var responseData = serializer.Deserialize(new StringReader(responseDataXDoc.ToString(SaveOptions.DisableFormatting)));
With Cinchoo ETL - an open source library, you can deserialize such xml easily as below
using (var r = ChoXmlReader<ResponseData>.LoadText(xml)
.WithXPath("//")
.WithXmlNamespace("diffgr", "")
.WithXmlNamespace("msdata", "")
)
{
var rec = r.FirstOrDefault();
rec.Print();
}
public class ResponseData
{
public string Status { get; set; }
public DateTime Time { get; set; }
public string Location { get; set; }
}
Sample fiddle: https://dotnetfiddle.net/HOOPOg
I have loaded the following XML file using xml.Load("myfile.xml"); where xml is of type XmlDocument:
<?xml version="1.0" encoding="ISO-8859-1"?>
<DTE xmlns="http://www.sii.cl/SiiDte" version="1.0">
<Documento ID="E000000005T033F0114525415">
<Encabezado>
<IdDoc>
<TipoDTE>33</TipoDTE>
<Folio>114525415</Folio>
<FchEmis>2021-11-02</FchEmis>
<FmaPago>1</FmaPago>
<FchVenc>2021-11-02</FchVenc>
</IdDoc>
</Encabezado>
</Documento>
</DTE>
How can I get Folionode?
I have tried with:
xml.DocumentElement.SelectSingleNode("/DTE/Documento/Encabezado/IdDoc/Folio");
xml.DocumentElement.SelectNodes("DTE/Documento/Encabezado/IdDoc/Folio")
xml.DocumentElement.SelectSingleNode("//DTE/Documento/Encabezado/IdDoc/Folio");
xml.DocumentElement.SelectSingleNode("/Documento/Encabezado/IdDoc/Folio");
xml.DocumentElement.SelectSingleNode("Documento/Encabezado/IdDoc/Folio");
xml.DocumentElement.SelectSingleNode("/Encabezado/IdDoc/Folio");
xml.DocumentElement.SelectNodes("/DTE/Documento/Encabezado/IdDoc/Folio")
when I debug xml.DocumentElement I see that the element is DTE so I think xml.DocumentElement.SelectSingleNode("Documento/Encabezado/IdDoc/Folio") should do it.
When I get xml.DocumentElement.FirstChild I get Documento node.
With xml.DocumentElement.FirstChild.FirstChild I get Encabezado node.
With xml.DocumentElement.FirstChild.FirstChild.FirstChild I get IdDoc node.
If I use xml.DocumentElement.FirstChild.FirstChild.FirstChild.SelectSingleNode("Folio"), returned value is null.
If I use xml.DocumentElement.FirstChild.FirstChild.FirstChild.ChildNodes, I get the 5 elements.
Then I could use xml.DocumentElement.FirstChild.FirstChild.FirstChild.ChildNodes[1].InnerText to get Folio value.
I can traverse the XML but, how can I do it to get the element directly?
Thanks
Jaime
It is better to use LINQ to XML API for your task. It is available in the .Net Framework since 2007.
The provided XML has a default namespace. It needs to be declared and used, otherwise it is imposable to find any XML element.
c#
void Main()
{
const string filename = #"e:\Temp\jstuardo.xml";
XDocument xdoc = XDocument.Load(filename);
XNamespace ns = xdoc.Root.GetDefaultNamespace();
string Folio = xdoc.Descendants(ns + "Folio")
.FirstOrDefault()?.Value;
Console.WriteLine("Folio='{0}'", Folio);
}
Output
Folio='114525415'
You can try to use the Xpath like below:
XmlDocument doc = new XmlDocument();
doc.Load("myfile.xml");
var node= doc.SelectSingleNode("Documento/Encabezado/IdDoc/[Folio='"114525415"']");
There are few ways to make things up with your issue.
So, we have our XML:
const string MyXML = #"<?xml version=""1.0"" encoding=""ISO-8859-1""?>
<DTE xmlns=""http://www.sii.cl/SiiDte"" version=""1.0"">
<Documento ID=""E000000005T033F0114525415"">
<Encabezado>
<IdDoc>
<TipoDTE>33</TipoDTE>
<Folio>114525415</Folio>
<FchEmis>2021-11-02</FchEmis>
<FmaPago>1</FmaPago>
<FchVenc>2021-11-02</FchVenc>
</IdDoc>
</Encabezado>
</Documento>
</DTE>";
And we need to get Folio node (exactly node, not just value). We can use:
XmlNamespaceManager:
to find descendant node(s) through XML namespace (xmlns) alias in XPath:
// Creating our XmlDocument instance
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(MyXML);
// Initializing XmlNamespaceManager and providing our xmlns with 'SiiDte' alias:
var xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
xmlNamespaceManager.AddNamespace("SiiDte", "http://www.sii.cl/SiiDte");
// Declaring our simple shiny XPath:
var xPath = "descendant::SiiDte:Folio";
// If we need single (first) element:
var folio = xmlDocument.DocumentElement.SelectSingleNode(xPath, xmlNamespaceManager);
// If we need all Folios:
var folios = xmlDocument.DocumentElement.SelectNodes(xPath, xmlNamespaceManager);
XDocument and its Descendants:
from System.Xml.Linq namespace and its XDocument class, to find descendant node(s) just by their tag name <Folio>:
// If we need single (first) element:
var folio = XDocument.Parse(MyXML)
.Descendants()
.FirstOrDefault(x => x.Name.LocalName == "Folio");
// Add System.Linq using to access FirstOrDefault extension method
// If we need all Folios - just replacing FirstOrDefault with Where extension method:
var folios = XDocument.Parse(MyXML)
.Descendants()
.Where(x => x.Name.LocalName == "Folio"); // and .ToList() if you need
// Or we can use also our XML namespace to filter Descendants:
var ns = (XNamespace)"http://www.sii.cl/SiiDte";
var folios = XDocument.Parse(MyXML).Descendants(ns + "Folio");
Deserialization:
to operate not with XML or nodes, but with some class (e.g. DTE), which represents your XML schema. I'm not sure that I totally understand your XML structure, but anyway as example it could be used.
So we create our classes, which are representation of our XML:
[Serializable, XmlRoot(ElementName = nameof(DTE), Namespace = "http://www.sii.cl/SiiDte")]
public class DTE
{
[XmlAttribute("version")]
public string Version { get; set; }
[XmlElement(nameof(Documento))]
public List<Documento> Documentacion { get; set; }
}
[Serializable]
public class Documento
{
[XmlAttribute(nameof(ID))]
public string ID { get; set; }
[XmlElement(nameof(Encabezado))]
public Encabezado Encabezado { get; set; }
}
[Serializable]
public class Encabezado
{
[XmlElement(nameof(IdDoc))]
public IDDoc IdDoc { get; set; }
}
[Serializable]
public class IDDoc
{
[XmlElement(nameof(TipoDTE))]
public int TipoDTE { get; set; }
[XmlElement(nameof(Folio))]
public long Folio { get; set; }
[XmlElement(nameof(FchEmis))]
public DateTime FchEmis { get; set; }
[XmlElement(nameof(FmaPago))]
public int FmaPago { get; set; }
[XmlElement(nameof(FchVenc))]
public DateTime FchVenc { get; set; }
}
Now we can easily create our DTE object with XmlSerializer class and its Deserialize method:
// Declaring our DTE object
var dte = (DTE)null;
using (var reader = new StringReader(MyXML))
{
dte = (DTE)new XmlSerializer(typeof(DTE)).Deserialize(reader);
}
Now we can get Folio as property of IdDoc class, which is property of Encabezado class, which in a turn is property of Documento class. Keeping in mind possible null result turns us to use, for example, null-propagation:
var folio = dte?.Documentacion.FirstOrDefault()?.Encabezado?.IdDoc?.Folio;
As Documentacion is a List<Documento> - we use again FirstOrDefault (also may be used ElementAtOrDefault(0)) to "imitate" SelectSingleNode. And for all Folios we can use Select (also with mull-propagation):
var folios = dte?.Documentacion.Select(x => x?.Encabezado?.IdDoc?.Folio);
Sure, we can edit properties if we want or add new:
// Edit
if (dte?.Documentacion.FirstOrDefault() is Documento documento)
documento.Encabezado.IdDoc.Folio = 112233445566;
// or create and add new
var newDocumento = new Documento
{
ID = "SomeID",
Encabezado = new Encabezado
{
IdDoc = new IDDoc
{
TipoDTE = 123,
Folio = 112233445566,
FmaPago = 1,
FchEmis = DateTime.Now,
FchVenc = DateTime.Now.AddDays(1)
}
}
};
dte.Documentacion.Add(newDocumento);
And finally save back to XML file using Serialization. Here became usable our class and properties attributes (e.g. [Serializable], [XmlElement] etc), which specifies how each property should be named or represented in XML:
using (var xmlWriter = XmlWriter.Create("My.xml",
new XmlWriterSettings
{
Encoding = Encoding.GetEncoding("ISO-8859-1"),
Indent = true
}))
{
// We remove default XSI, XSD namespaces and leave only our custom:
var xmlSerializerNamespaces = new XmlSerializerNamespaces();
xmlSerializerNamespaces.Add("", "http://www.sii.cl/SiiDte");
// and saving our DTE object to XML file.
xmlSerializer.Serialize(xmlWriter, dte, xmlSerializerNamespaces);
}
Remarks
Of course, parse of XML strings could be replaces with loading XML files (by FileStreams) if needed. And of course you can edit DTE (and child) classes with other properties and mark them as XML attributes or XML elements or making collections with XmlArray and XmlArrayItem attributes - whatever, depending on your needs. Also notice about null XML nodes or its values and take care about it with, for example, Nullable<T> (e.g. long?, DateTime?), IsNullable property of XML attributes and some kind of value validation at property setter:
private long _folio;
[XmlElement(nameof(Folio), IsNullable = true)]
public long? Folio
{
get => _folio;
set => _folio = value ?? 0L; // Null-coalescing with default fallback value of 0L
}
Hope it would be helpful for your future purposes.
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);
}
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>";
I was using this Xml Deserialization with complex elements in c# as a reference. I've noticed there are a lot of threads about Deserializing xml, but they don't mention when a tag has multiple values within it.
I am trying to deserialize my xml for lvl3 into an array of objects.
I'm getting a "there is an error in the xml document (1, 2)" error.
I have an xml string that I'm retrieving via an HTTP GET request that is formatted like this:
<xml ...>
<lvl1 id="xxx" name="yyy">
<lvl2 id="aaa" name="bbb">
<lvl3 id="mmm" name="nnn" val1="ppp" val2="qqq">
<lvl4a x="000" y="000" z="000" />
<lvl4b a="000" b="000" c="000" />
<lvl4c l="000" w="000" h="000" />
...
</lvl3>
</lvl2>
</lvl1>
</xml>
I have the following code that keeps throwing an exception:
"An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.dll"
The exception is thrown by this line:
temp = (Test)new XmlSerializer(typeof(Test)).Deserialize(rdr);
But I'm not sure how to go about debugging it to find the error. Here is the complete code:
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(xmlstring);
XmlNodeList list = xmldoc.GetElementsByTagName("lvl2");
for (int i = 0; i < list.Count; i++)
{
Test temp = new Test();
using (XmlReader rdr = XmlReader.Create(new StringReader(list[i].InnerXml)))
{
temp = (Test)new XmlSerializer(typeof(Test)).Deserialize(rdr); // exception thrown here
}
Console.WriteLine(temp.id);
Console.WriteLine(temp.overall.x);
}
[XmlRoot("lvl3")]
public class Test{
[XmlAttribute("id")]
public string id { get; set; }
[XmlAttribute("name")]
public string name { get; set; }
[XmlElement(ElementName = "lvl4a")]
public Overall overall { get;set; }
}
public class Overall
{
[XmlAttribute("x")]
public string x { get; set; }
[XmlAttribute("y")]
public string y { get;set; }
[XmlAttribute("z")]
public string z { get;set; }
}
Either fix your Test class to include public properties for val1 and val2 attributes or remove them from the xml. The schema of xml should match the class structure.