I have a site that imports bookings from an external XML file and stores them as nodes in an Umbraco site.
The issue:
Basically, The system in which I exported the bookings from has changed how it exports the XML and the original root node.
I am assuming this is an issue related to this root node as many answers on stack have mentioned this in regards to the error in the subject title, but none that I could see have covered how to deal with a dynamic rootnode.
What I used to have in the XML file:
<Bookings>
<row id="1">
<FirstBookingAttribute>....
</row>
</Bookings>
Now is dynamic and can end up looking like:
<BookingsFebruary2017 company="CompanyNameHere">
<row id="1">
<firstBookingAttribute>....
</row>
</BookingsFebruary2017>
Because of this I am not entirely sure how to set the XMLroot node in C#.
Incase this issue is not related to the root node, I have pasted the erroring code below:
Controller File
var reader = new StreamReader(tempFile);
var deserializer = new XmlSerializer(typeof(Bookings));
var xml = deserializer.Deserialize(reader);
var studentBookings = (Bookings) xml;
Model of Bookings
public class Bookings
{
[XmlElement("row")]
public List<Booking> StudentBookings { get; set; }
}
Thank you
XmlSerializer doesn't allow for dynamic root names out of the box. The expected root name is fixed, and is defined by the type name, the presence of an [XmlRoot] or [XmlType] attribute, and possibly the XmlSerializer constructor used.
Thus an easy solution is to pre-load the XML into an XDocument, modify the root name, and then deserialize:
XDocument doc;
using (var xmlReader = XmlReader.Create(tempFile))
{
doc = XDocument.Load(xmlReader);
}
var rootName = doc.Root.Name;
doc.Root.Name = "Bookings";
var deserializer = new XmlSerializer(typeof(Bookings));
Bookings bookings;
using (var xmlReader = doc.CreateReader())
{
bookings = (Bookings)deserializer.Deserialize(xmlReader);
}
bookings.RootName = rootName.LocalName;
Note the use of CreateReader() to deserialize directly from the loaded XML.
Another option would be to create a custom XmlSerializer for each file using new XmlSerializer(typeof(Bookings), new XmlRootAttribute { ElementName = rootName }), however there are two issues with this:
Each serializer so created must be cached in a hash table to prevent a severe memory leak (loading of duplicate dynamically created assemblies), as is explained in this answer and also the documentation.
But even if you do this, if you are reading many different files with many different root names, your memory use will grow rapidly and continually due to constant loading of new unique dynamically created XmlSerializer assemblies.
However, if you only had to load one single file, you could do something like this:
Bookings bookings = null;
using (var xmlReader = XmlReader.Create(tempFile))
{
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element)
{
var rootName = xmlReader.LocalName;
var deserializer = new XmlSerializer(typeof(Bookings), new XmlRootAttribute { ElementName = rootName });
bookings = (Bookings)deserializer.Deserialize(xmlReader);
bookings.RootName = rootName;
break;
}
}
}
If both solutions require too much memory, you could consider creating a subclassed XmlTextReader that allows for the the root element to be renamed on the fly while reading.
In both cases I modified Bookings to look as follows:
public class Bookings
{
[XmlIgnore]
public string RootName { get; set; }
[XmlAttribute("company")]
public string Company { get; set; }
[XmlElement("row")]
public List<Booking> StudentBookings { get; set; }
}
Related
I have a xml schema in the form of
<?xml version="1.0" encoding="UTF-8"?>
<project ref="edward" name="Edward(A)">
<desc/>
<Zones>
<Zone ref="1" name="Zone1"/>
<Zone ref="2" name="Zone2"/>
<Zone ref="3" name="Zone3"/>
<Zone ref="4" name="Zone4"/>
</Zones>
</project>
I am trying to extract all the Zones value using Xml to Linq (not an expert)
I tried
string xmlString = System.IO.File.ReadAllText("..\\..\\..\\v1.xml");
XDocument xdoc = new XDocument();
xdoc=XDocument.Parse(xmlString);
var ele = xdoc.Descendants("Zones")
.Select(x => (string)x.Element("name"))
.FirstOrDefault();
// this is null
var result = xdoc.Element("project").Descendants("Zones").Descendants("Zone");
foreach (var item in result)
{
Console.WriteLine(item.name); //what should be here
}
You could add the items to a list or using a Dictionary<int, string> may look something like...
Dictionary<int, string> Zones = new Dictionary<int, string>();
foreach (var item in result) {
int.TryParse(item.Attribute("ref").Value, out int value);
Zones.Add(value, item.Attribute("name").Value);
Console.WriteLine(item.Attribute("name").Value);
Console.WriteLine(item.Attribute("ref").Value);
}
Another approach using two simple Classes…
Looking closer, I am betting using a couple of classes and the System.Xml.Serialization; library may make this easier. Given the XML file. One Class appears obvious… called a Zone object with the int and string properties. Then another Class we could call ZoneProject.
The ZoneProject Class would have three (3) properties. In this case two string properties called Ref and Name, and a List<Zone> of Zone objects called Zones. The bare minimum of these two classes may look something like…
The Zone Class
public class Zone {
[XmlAttribute("ref")]
public int Ref { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
}
The ZoneProject Class
[XmlRoot("project")]
public class ZoneProject {
[XmlAttribute("ref")]
public string Ref { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
[XmlArray("Zones")]
public List<Zone> Zones { get; set; }
}
To help “deserialize” the XML we added additional qualifiers to help mate the XML element/attributes to the particular property of each class. The XmlRoot is the base class ZoneProject with the XmlAttributes and the collection of Zone objects using the XmlArray qualifier. This property will be a List<Zone> collection. The same follows for the Zone Class.
With this set up, we can use the System.Xml.Serialization; library and deserialize the XML file into a single ZoneProject object. Something like…
string filePath = #"PathToYourXML_File\file.xml";
XmlSerializer serializer = new XmlSerializer(typeof(ZoneProject));
ZoneProject TheZoneProject;
using (FileStream fs = File.OpenRead(filePath)) {
TheZoneProject = (ZoneProject)serializer.Deserialize(fs);
}
It should be noted, that the answer from #Enyra on the SO question How to read XML file into List<>? …was helpful in the answer above.
I hope this makes sense.
Please check your xml file path. I think that , problem here.
string xmlString = System.IO.File.ReadAllText(dicorrectPath);
You can collect you zones attributes with Linq:
var result = xdoc.Element("project").Descendants("Zones").Descendants("Zone");
var zonesAtributes = result.Select(x =>
new
{
name = x.Attribute("name").Value,
#ref = x.Attribute("ref").Value
}).ToArray();
I have two different XML documents. Their structure is almost identical, but they have some different elements.
I would like to deserialize the incoming documents in to one class which is a superset of both classes. There is no need to ever serialize the class, I only need to deserialize the documents.
The XML document types have a different root element, lets say the root of the first is <CLASSA>and the other is <CLASSB>.
I am looking for something like this, where both <CLASSA> and <CLASSB> xml documents are mapped to ClassAandB:
[XmlRoot(ElementName="CLASSA,CLASSB")]
public class ClassAandB {
[XmlElement(ElementName="syntaxid")]
public Syntaxid Syntaxid{ get; set; }
[XmlElement(ElementName="email")]
public Email Email { get; set; }
[XmlElement(ElementName="envelope")]
public Envelope Envelope { get; set; }
[XmlElement(ElementName="header")]
public Header Header { get; set; }
}
I can then find out which of the two types it is by reading the Syntaxid property. This helps me because a lot of the processing is the same for both types.
Any suggestions how to do this?
Since the xml root element name might depend on the content of the xml document, you'll have to configure the XmlSerializer at runtime with this xml root element name to be used.
In this case, there's no need anymore to apply an XmlRootAttribute.
This can be done via the constructor overload accepting an XmlRootAttribute argument, via which you pass the root element name.
public XmlSerializer (Type type, System.Xml.Serialization.XmlRootAttribute root);
You might know the root element name in front eg. depending on the source of the xml document, or you might discover it at runtime from the xml document itself.
The following from the example below shows how the xml root element name gets set.
String rootName = "CLASSA"; // "CLASSB"
var serializer = new XmlSerializer(typeof(ClassAandB), new XmlRootAttribute(rootName));
An simplified example using an XmlReader as source and retrieving the root xml element name from the content.
public class ClassAandB
{
[XmlElement(ElementName="syntaxid")]
public String Syntaxid{ get; set; }
[XmlElement(ElementName="email")]
public String Email { get; set; }
[XmlElement(ElementName="header")]
public String Header { get; set; }
}
var classA = Deserialize(XmlReader.Create(
new StringReader("<CLASSA><syntaxid>A</syntaxid></CLASSA>"))
);
Console.WriteLine(classA.Syntaxid); // A
var classB = Deserialize(
XmlReader.Create(new StringReader("<CLASSB><syntaxid>B</syntaxid></CLASSB>"))
);
Console.WriteLine(classB.Syntaxid); // B
public static ClassAandB Deserialize(XmlReader reader)
{
reader.MoveToContent();
string rootName = reader.Name;
var serializer = new XmlSerializer(typeof(ClassAandB),
new XmlRootAttribute(rootName)
);
var deserialized = serializer.Deserialize(reader) as ClassAandB;
return deserialized;
}
I suggest you to remove XmlRoot attribute and use:
var doc = new XmlDocument();
doc.Load("file.xml");
XmlElement root = xmlDoc.DocumentElement;
var serializer = new XmlSerializer(typeof(ClassAandB), new XmlRootAttribute(root.ToString()));
I try to read an xml file with almost 8 leves of child nodes but without succsses.
I try using LINQ, SelectNode, SelectSingleNode, try to use a dumb foreach over XMLNodeList.
In my last intent I use this dumb foreach over the XMLNodeList to try to catch the text or value of some nodes.
These nodes are in diferents levels of deep but I can get onle the first element in sequesnse but the rest only repeat the firs value.
This is part of my code.
XmlDocument xDocXML = new XmlDocument();
xDocXML.Load(file_name);//file_name is a string with the full path of the file
XmlNodeList Article = xDocXML.SelectNodes("/ArticleSet/Article/Document"); //We get the Document of the article
foreach(XmlNode n in Article)
{
spmid = n["ID"].InnerText;
liga = string.Concat(TestString1, spmid);
//Test 1
//stitle = n.SelectSingleNode("//Article/ArticleTitle").InnerText;
//Test 2
//stitle = n["//Article/ArticleTitle"].InnerText;
XmlNode titles = n.SelectSingleNode("//Article/ArticleTitle");
stitle = titles.InnerText;//This line only work once and it repeat in all xmlnodes read
camposcuenta = camposcuenta + 1;
dt_abstractdb.Rows.Add(new Object[] { camposcuenta.ToString(), spmid, stitle, sresum, liga, ligaPDF, ligadoi });
}
Any suggestion over this
Without knowing what your XML looks like, I would recomend creating a class to represent your XML file and then use serialization. With this solution you can have multiple levels and let the Framework handle them.
Check this for example: How to Deserialize XML document
You can also use an external tool to generate your POCO classes, for exemple: https://xmltocsharp.azurewebsites.net/
Example code from the link's solution:
Classes to represent your XML:
[Serializable()]
public class Car
{
[System.Xml.Serialization.XmlElement("StockNumber")]
public string StockNumber { get; set; }
[System.Xml.Serialization.XmlElement("Make")]
public string Make { get; set; }
[System.Xml.Serialization.XmlElement("Model")]
public string Model { get; set; }
}
[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
[XmlArray("Cars")]
[XmlArrayItem("Car", typeof(Car))]
public Car[] Car { get; set; }
}
Reading code:
CarCollection cars = null;
string path = "cars.xml";
XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();
I am retrieving XML documents from a web service I have no control over. The XML is formatted similarly to the following:
<?xml version="1.0"?>
<ns:obj xmlns:ns="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</ns:obj>
where the root node is in the "ns" namespace, but none of its child elements are.
After some trial and error, I found that I could deserialize the document to a C# object by doing the following:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
public class xmlObject
{
[XmlElement(Namespace = "")]
public string address { get; set; }
[XmlElement(Namespace = "")]
public int residents { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\"?>" +
"<ns:obj xmlns:ns=\"somenamespace\">" +
" <address>1313 Mockingbird Lane</address>" +
" <residents>5</residents>" +
"</ns:obj>";
var serializer = new XmlSerializer(typeof(xmlObject));
using (var reader = new StringReader(xml))
{
var result = serializer.Deserialize(reader) as xmlObject;
Console.WriteLine("{0} people live at {1}", result.residents, result.address);
// Output: "5 people live at 1313 Mockingbird lane"
}
}
}
If I omit the XmlElementAttribute on the individual members, I instead get an empty object. I.e. The output reads
0 people live at
(result.address is equal to null.)
I understand the rationale behind why the deserialization process works like this, but I'm wondering if there is a less verbose way to tell XmlSerializer that the child elements of the object are not in the same namespace as the root node.
The objects I'm working with in production have dozens of members and, for cleanliness sake, I'd like to avoid tagging all of them with [XmlElement(Namespace = "")] if it's easily avoidable.
You can combine XmlRootAttribute with XmlTypeAttribute to make it so the root element, and the root element's elements, have different namespaces:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
[XmlType(Namespace="")]
public class xmlObject
{
public string address { get; set; }
public int residents { get; set; }
}
Using the type above, if I deserialize and re-serialize your XML I get:
<q1:obj xmlns:q1="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</q1:obj>
Sample fiddle.
If you know the contract with the web service, why not use a DataContractSerializer to deserialize the XML into the objects?
I want to write a xml file with C#. It is a basic file like that :
<EmployeeConfiguration>
<Bosses>
<Boss name="BOB">
<Employees>
<Employee Id="#0001" />
<Employee Id="#0002" />
</Employees>
<Boss>
</Bosses>
</EmployeeConfiguration>
and I don't want have an Employees node if there is not Employee node...
I want use XElement but I can't because of that... So I used XmlWriter. it works fine but I find it's very verbose to write XML :
EmployeeConfiguration config = EmployeeConfiguration.GetConfiguration();
using (XmlWriter writer = XmlWriter.Create(_fileUri, settings))
{
writer.WriteStartDocument();
// <EmployeeConfiguration>
writer.WriteStartElement("EmployeeConfiguration");
if (config.Bosses.Count > 0)
{
// <Bosses>
writer.WriteStartElement("Bosses");
foreach (Boss b in config.Bosses)
{
// Boss
writer.WriteStartElement("Boss");
writer.WriteStartAttribute("name");
writer.WriteString(b.Name);
writer.WriteEndAttribute();
if (b.Employees.Count > 0)
{
writer.WriteStartElement("Employees");
foreach (Employee emp in b.Employees)
{
writer.WriteStartElement(Employee);
writer.WriteStartAttribute(Id);
writer.WriteString(emp.Id);
writer.WriteEndAttribute();
writer.WriteEndElement();
}
}
}
}
}
Is there another (and fastest) way to write this kind of xml file ?
If you mean "fastest" as the fastest way to write some code to do it (and the simplest), then creating a custom class and serializing it using the XmlSerializer is the way to go...
Create your classes as follows:
[XmlRoot("EmployeeConfiguration")]
public class EmployeeConfiguration
{
[XmlArray("Bosses")]
[XmlArrayItem("Boss")]
public List<Boss> Bosses { get; set; }
}
public class Boss
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlArray("Employees")]
[XmlArrayItem("Employee")]
public List<Employee> Employees { get; set; }
}
public class Employee
{
[XmlAttribute]
public string Id { get; set; }
}
and then you can serialize these out with this:
// create a serializer for the root type above
var serializer = new XmlSerializer(typeof (EmployeeConfiguration));
// by default, the serializer will write out the "xsi" and "xsd" namespaces to any output.
// you don't want these, so this will inhibit it.
var namespaces = new XmlSerializerNamespaces(new [] { new XmlQualifiedName("", "") });
// serialize to stream or writer
serializer.Serialize(outputStreamOrWriter, config, namespaces);
As you can see - using various attributes on the classes instruct the serializer in how it should serialize the class. Some of the ones I've included above are actually the default settings and don't explicitly need to be stated - but I've included them to show you how it is done.
You might want to look at XML serialization, using the XmlElement, XmlAttribute (and so on) attributes. I think this gives you the level of control you want, and a very quick, safe and easy to maintain call to do the XML conversion.
var xml = new XElement("EmployeeConfiguration",
new XElement("Bosses"),
new XElement("Boss", new XAttribute("name", "BOB"),
new XElement("Employees"),
new XElement("Employee", new XAttribute("Id", "#0001")),
new XElement("Employee", new XAttribute("Id", "#0001"))
)
);