How to change a value inside XML file using c# Visual Studio - c#

I am trying to change values inside XML file. This is my XML "person.xml" file:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Table>
<Person>
<Number>1</Number>
<Name>Mariano</Name>
<Last Name>Italiano</Last Name>
<Age>36</Age>
</Person>
<Person>
<Number>2</Number>
<Name>John</Name>
<Last Name>Smith</Last Name>
<Age>32</Age>
</Person>
<Person>
<Number>3</Number>
<Name>Bob</Name>
<Last Name>Leckie</Last Name>
<Age>50</Age>
</Person>
<Person>
<Number>4</Number>
<Name>Patrick</Name>
<Last Name>Collins</Last Name>
<Age>63</Age>
</Person>
</Table>
And i want my program to do some things:
Find a name written in textBox2->Text. Then if the name exists i want to change it to name written in textBox3->Text.
Remove (if found) whole Person if CheckBoxDelete is set to true
I know how to create such a XML file (found an example somewhere) but i can't find a solution how to find and remove if neccessary.
I am using Visual Studio 2015 if it matters.
Thank You.

You can use XDocument to replace the value and delete the person according to the Name.
Code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public string filename = "D:\\test.xml";
private void BtnRePlace_Click(object sender, EventArgs e)
{
XDocument doc = XDocument.Load(filename);
doc.Descendants("Person").Descendants("Name").Where(i => i.Value == txtReplaceFirstName.Text).FirstOrDefault().SetValue(txtReplaceLastName.Text);
doc.Save(filename);
}
private void btndelete_Click(object sender, EventArgs e)
{
XDocument doc = XDocument.Load(filename);
doc.Descendants("Person").Where(i => i.Element("Name").Value == txtReplaceFirstName.Text&& i.Element("LastName").Value == txtReplaceLastName.Text).FirstOrDefault().Remove();
doc.Save(filename);
}
}
Besides, your xml file have an error, the Last Name should be LastName without space.

You could map (i.e. deserialize) the data to POCO objects, manipulate the data and serialize again. Might be a bit overkill though. As others stated, using XDocument might be good enough.
Here is a minimal example:
public class Person
{
[XmlElement]
public int Number { get; set; }
[XmlElement]
public string Name { get; set; }
[XmlElement]
public string LastName { get; set; }
[XmlElement]
public int Age { get; set; }
public override string ToString() => $"{Name} {LastName} is {Age}";
}
[XmlRoot("Table")]
public class RootObject
{
[XmlElement("Person")]
public List<Person> Persons;
}
class Program
{
static void Main(string[] args)
{
var xmlSer = new XmlSerializer(typeof(RootObject));
using var fs = new FileStream("input.xml", FileMode.Open);
var doc = (RootObject)xmlSer.Deserialize(fs);
foreach (var p in doc.Persons)
{
System.Console.WriteLine(p);
}
// do whatever with the RootObject instance
}
}

XML is the object, so consider re-using existing objects before creating more POCOs.
Here's code that shows how to find, delete, and update elements in XML. Take some time to learn XPath, it's very powerful, flexible, and available across nearly every platform.
using System;
using System.Threading.Tasks;
using System.Xml;
namespace testconsole
{
class Program
{
public static string strFileName = "c:\\temp\\test.xml";
static void Main(string[] args) {
XmlDocument xml = new XmlDocument();
xml.Load(strFileName);
string strMatchName = "Mariano";
string strXPath = "/Table/Person[Name='" + strMatchName + "']";
XmlElement ndPerson = (XmlElement)xml.SelectSingleNode(strXPath);
if (ndPerson != null) {
// Delete if the person is found
ndPerson.ParentNode.RemoveChild(ndPerson);
}
string strNewName = "Bob";
strXPath = "/Table/Person/Name[.='" + strNewName + "']";
XmlElement ndName = (XmlElement)xml.SelectSingleNode(strXPath);
if (ndName != null) {
ndName.InnerText = "Robert"; // new name
}
xml.Save(strFileName);
}
}
}

Related

C# serialize object to XML with element containing XML text without escaping

I searched and tried some attributes but none of them worked for my below scenario:
public class ObjSer
{
[XmlElement("Name")]
public string Name
{
get; set;
}
}
//Code to serialize
var obj = new ObjSer();
obj.Name = "<tag1>Value</tag1>";
var stringwriter = new System.IO.StringWriter();
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(stringwriter, obj);
Output would be as follows:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
But I need output as:
<ObjSer><Name><tag1>Value</tag1></Name></ObjSer>
Scenario 2: In some cases I need to set:
obj.Name = "Value";
Is there any attribute or method I can override to make it possible?
You can't with default serializer. XmlSerializer does encoding of all values during serialization.
If you want your member to hold xml value, it must be XmlElement. Here is how you can accomplish it
public class ObjSer
{
[XmlElement("Name")]
public XmlElement Name
{
get; set;
}
}
var obj = new ObjSer();
// <-- load xml
var doc = new XmlDocument();
doc.LoadXml("<tag1>Value</tag1>");
obj.Name = doc.DocumentElement;
// --> assign the element
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
Output:
<?xml version="1.0" encoding="IBM437"?>
<ObjSer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>
<tag1>Value</tag1>
</Name>
</ObjSer>
UPDATE:
In case if you want to use XElement instead of XmlElement, here is sample on how to do it
public class ObjSer
{
[XmlElement("Name")]
public XElement Name
{
get; set;
}
}
static void Main(string[] args)
{
//Code to serialize
var obj = new ObjSer();
obj.Name = XDocument.Parse("<tag1>Value</tag1>").Document.FirstNode as XElement;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
serializer.Serialize(Console.Out, obj);
}
No, you can't. It is the natural and usual behaviour of the xml serializer. The serializer doesn't have to handle within the XML strings. So, it escapes the xml as expected.
You should decode the escaped string again while deserializing it.
If you want to add dynamically elements in the XML I suggest you to use Linq to XML and you can create tag1 or another kind of elements easily.
I would suggest serializing to an XDocument then converting that to a string and manually unescaping the desired part and writing it to a file. I would say this would give you the least headache it shouldn't be more than a couple lines of code. If you need it I can provide some code example if needed.
I found one more way of changing the type
public class NameNode
{
public string tag1
{
get; set;
}
[XmlText]
public string Value
{
get; set;
}
}
public class ObjSer
{
[XmlElement("Name")]
public NameNode Name
{
get; set;
}
}
To set value of Name:
var obj = new ObjSer();
var valueToSet = "<tag1>Value</tag1>";
//or var valueToSet = "Value";
//With XML tag:
if (valueToSet.Contains("</"))
{
var doc = new XmlDocument();
doc.LoadXml(valueToSet);
obj.Name.tag1 = doc.InnerText;
}
else //Without XML Tags
{
obj.Name.Value = senderRecipient.Name;
}
This solution will work in both cases, but has limitation. It will work only for predefined tags (ex. tag1)

Importing User Definitions from XML

I have been trying to make an export/import program but when I try to import the XML-information to the textbox it doesn't work.
C# snippet from program:
XmlDocument doc = new XmlDocument();
doc.Load(open.FileName);
foreach (XmlNode x in doc.DocumentElement)
textBox6.Text = x["Contact"].Value;
and the XML-file is as follows:
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<Table1>
<Contact>example</Contact>
</Table1>
</NewDataSet>
Original image:
http://i.stack.imgur.com/0ks2F.png
try use InnerText instead of value
textBox6.Text = x["Contact"].InnerText;
A breakpoint on the line
textBox6.Text = x["Contact"].Value;
should be revealing...
That's just based on visual inspection - people who supply XML as png files don't get it examined by me lol...
You can use XmlSerializer to achieve same operation.
Checkout following code.
using System.Xml.Serialization;
using System.IO;
namespace DemoApplication
{
class Program
{
static void Main(string[] args)
{
NewDataSet objNewDataSet = new NewDataSet();
Table objTable = new Table();
objTable.Conact = "Hello";
objNewDataSet.Table1 = objTable;
StreamWriter objStream = new StreamWriter("C:\\Users\\Nirav Kamani\\Desktop\\abc.xml");
XmlSerializer objXmlSerializer = new XmlSerializer(typeof(NewDataSet));
objXmlSerializer.Serialize(objStream, objNewDataSet);
}
}
}
Model Classes.
using System.Xml.Serialization;
namespace DemoApplication
{
public class NewDataSet
{
[XmlElement]
public Table Table1 { get; set; }
}
}
namespace DemoApplication
{
public class Table
{
public string Conact { get; set; }
}
}
You can serialize and deserialize easily.
For more information check out following links.
I am just giving you a better approach to achieve same operation in terms of objects.
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx

XML Serialization

I want to add multiple record one by one using XML serialization. I have three text boxes and a button which is used to get the data from the user and then serializae in XML file. However, when I add one record on it's own it's fine, but for the another record it declares multiple root elements which I am not able to handle.
I am doing XML serialization and I am getting this error in XML File
**<?xml version="1.0" encoding="utf-8"?>
<sroot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:my-examples:shaping">**
<CustomerId>3a8bb49e-f616-49a5-8ec8-1886881c3042</CustomerId>
<FirstName>HASEEB</FirstName>
<LastName>KHAN</LastName>
<CustomerEmail>SDCFDS</CustomerEmail>
**</sroot><?xml version="1.0" encoding="utf-8"?>
<sroot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:my-examples:shaping">**
<CustomerId>6d2cbe5e-e1fb-4526-9f98-bf396b4ded55</CustomerId>
<FirstName>ammae</FirstName>
<LastName>wdfjk</LastName>
<CustomerEmail>sdkcbnk</CustomerEmail>
</sroot>
As you can see in above XML code that there are multiple root element is written and I am not able to fix that I have a class called customer.cs and the code is written in this class is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
namespace XmlSearlizeProject.Classes
{
[Serializable]
[XmlRoot(ElementName = "sroot", Namespace = "urn:my-examples:shaping")]
public class Customer
{
string id = default(string);
string firstName = default(string);
string lastName = default(string);
string email = default(string);
public Customer()
{
}
public Customer(string id, string firstName, string lastName, string email)
{
CustomerId = id;
FirstName = firstName;
LastName = lastName;
Email = email;
}
[XmlElement("CustomerId")]
public string CustomerId
{
get
{
return this.id;
}
set
{
this.id = value;
}
}
[XmlElement("FirstName")]
public string FirstName
{
get
{
return this.firstName;
}
set
{
this.firstName = value;
}
}
[XmlElement("LastName")]
public string LastName
{
get
{
return this.lastName;
}
set
{
this.lastName = value;
}
}
[XmlElement("CustomerEmail")]
public string Email
{
get
{
return this.email;
}
set
{
this.email = value;
}
}
}
}
And my C# code is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Text;
namespace XmlSearlizeProject.WebPages
{
public partial class CustomerPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
private void GeneralFunction(Stream xmlStream)
{
//xmlStream.Close();
string customerId = Guid.NewGuid().ToString();
Classes.Customer customer = new Classes.Customer(customerId,this.FirstNameTextBox.Text,this.LastNameTextBox.Text,this.EmailTextBox.Text);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(Classes.Customer));
XmlDocument document = new XmlDocument();
document.Load(xmlStream);
XmlElement id = document.CreateElement("Id");
id.InnerText = customerId;
document.DocumentElement.InsertAfter(id, document.DocumentElement.LastChild);
XmlElement firstName = document.CreateElement("rtgr");
firstName.InnerText = customer.FirstName;
document.DocumentElement.InsertAfter(firstName, document.DocumentElement.LastChild);
XmlElement lastName = document.CreateElement("rtgtr");
lastName.InnerText = customer.LastName;
document.DocumentElement.InsertAfter(lastName, document.DocumentElement.LastChild);
XmlElement email = document.CreateElement("grbfr");
email.InnerText = customer.Email;
document.DocumentElement.InsertAfter(email, document.DocumentElement.LastChild);
XmlTextWriter xmlTextWriter = new XmlTextWriter(xmlStream, Encoding.UTF8);
xmlSerializer.Serialize(xmlTextWriter, customer);
xmlStream.Close();
}
private void SerializeCustomer()
{
if (File.Exists(Server.MapPath("~/Customer.xml")))
{
Stream xmlWriterStream = new FileStream(Server.MapPath("~/Customer.xml"), FileMode.Open, FileAccess.ReadWrite);
GeneralFunction(xmlWriterStream);
xmlWriterStream.Close();
}
}
private void DeSerializeCustomer()
{
Stream xmlReaderStream = new FileStream(Server.MapPath("~/Customer.xml"), FileMode.Open, FileAccess.Read, FileShare.Read);
XmlSerializer xmlDeSerializer = new XmlSerializer(typeof(Classes.Customer));
Classes.Customer customer = (Classes.Customer)xmlDeSerializer.Deserialize(xmlReaderStream);
}
protected void SubmitButton_Click(object sender, EventArgs e)
{
//if (!File.Exists(Server.MapPath("~/Customer.xml")))
//{
SerializeCustomer();
//}
//else
//{
// DeSerializeCustomer();
//}
}
}
}
It looks like you're serializing one customer at a time, versus serializing a list/array/collection of customers to the XML file.
Serializing one works because you have 1 root element - Customer. However, when serializing many, you will need to serializing the collection of customers to the XML file.
So then you'll have (example purposes only):
<Customers>
<sroot/>
<sroot/>
</Customers>
A few articles to look at on this:
C# Serializing a Collection of Objects
http://wcode.net/2009/08/serialize-collection-of-object-in-c/
http://www.codeproject.com/KB/cs/objserial.aspx
Define a schema definition (xsd) in the format you want your xml to look like. Then you can use some external tools like xsd2code. This would Auto-generate your "Customer" class to the format of your xsd. (In case if you are doing in manually). Then your xml will match the way you define in your xsd. Give it a try, defining a xsd for your xml is always a better practice.
You could just inherit from list something like this...
[Serializable]
[XmlRoot(ElementName = "sroot", Namespace = "urn:my-examples:shaping")]
public class CustomerList : List<Customer>
{
}
[Serializable]
public class Customer
{
...
}
Example usage...
CustomerList customerList = new CustomerList
{
new Customer
{
FirstName = "John",
LastName = "Doe",
Email = "johndoe#foobar.com",
CustomerId = "123"
},
new Customer
{
FirstName = "Jane",
LastName = "Doe",
Email = "janedoe#foobar.com",
CustomerId = "456"
}
};
Then your method would serialize the list (instance of CustomerList)...
SerializeCustomerList(CustomerList customers)
{
// Do serialize CustomerList instance...
}
Then the resulting XML would look like...
<sroot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:my-examples:shaping">
<Customer>
<CustomerId>123</CustomerId>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<CustomerEmail>johndoe#foobar.com</CustomerEmail>
</Customer>
<Customer>
<CustomerId>456</CustomerId>
<FirstName>Jane</FirstName>
<LastName>Doe</LastName>
<CustomerEmail>janedoe#foobar.com</CustomerEmail>
</Customer>
</sroot>
Hope it helps!

Serialize List<T> to XML, and reverse the XML to List<T>

Does anyone know how I (or if it's possible to) reverse the XML I'm creating below
[Serializable()]
public class CustomDictionary
{
public string Key { get; set; }
public string Value { get; set; }
}
public class OtherClass
{
protected void BtnSaveClick(object sender, EventArgs e)
{
var analysisList = new List<CustomDictionary>();
// Here i fill the analysisList with some data
// ...
// This renders the xml posted below
string myXML = Serialize(analysisList).ToString();
xmlLiteral.Text = myXML;
}
public static StringWriter Serialize(object o)
{
var xs = new XmlSerializer(o.GetType());
var xml = new StringWriter();
xs.Serialize(xml, o);
return xml;
}
}
The xml rendered
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfCustomDictionary xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CustomDictionary>
<Key>Gender</Key>
<Value>0</Value>
</CustomDictionary>
<CustomDictionary>
<Key>Height</Key>
<Value>4</Value>
</CustomDictionary>
<CustomDictionary>
<Key>Age</Key>
<Value>2</Value>
</CustomDictionary>
</ArrayOfCustomDictionary>
Now, after a few hours of Googling and trying I'm stuck (most likely my brain have some vacation already). Can anyone help me how to reverse this xml back to a List?
Thanks
Just deserialize it:
public static T Deserialize<T>(string xml) {
var xs = new XmlSerializer(typeof(T));
return (T)xs.Deserialize(new StringReader(xml));
}
Use it like this:
var deserializedDictionaries = Deserialize<List<CustomDictionary>>(myXML);
XmlSerializer.Deserialize

XmlSerializer List Item Element Name

I have a class PersonList
[XmlRoot("Persons")]
PersonList : List<Human>
when I serialize this to XML, by default it will produce something like this:
<Persons>
<Human>...</Human>
<Human>...</Human>
</Persons>
My question is what needs to be done in order to change element Human to Person in the output? so the output would be :
<Persons>
<Person>...</Person>
<Person>...</Person>
</Persons>
and, how to deserialize the above XML to the PersonList class object?
Per Nick's advice, Here is my testing code:
[XmlRoot("Persons")]
public class Persons : List<Human>
{
}
[XmlRoot("Person")]
public class Human
{
public Human()
{
}
public Human(string name)
{
Name = name;
}
[XmlElement("Name")]
public string Name { get; set; }
}
void TestXmlSerialize()
{
Persons personList = new Persons();
personList.Add(new Human("John"));
personList.Add(new Human("Peter"));
try
{
using (StringWriter writer = new StringWriter())
{
XmlSerializer serializer = new XmlSerializer(typeof(Persons));
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
XmlWriter xmlWriter = XmlWriter.Create(writer, settings);
serializer.Serialize(xmlWriter, personList, namespaces);
Console.Out.WriteLine(writer.ToString());
}
}
catch (Exception e)
{
Console.Out.WriteLine( e.ToString());
}
}
The output of the testing code is:
<Persons>
<Human>
<Name>John</Name>
</Human>
<Human>
<Name>Peter</Name>
</Human>
</Persons>
As the output shows, the [XmlRoot("Person")] on Human does not change the tag to Person from Human.
Mark your class with the following attributes:
[XmlType("Account")]
[XmlRoot("Account")]
The XmlType attribute will result in the output requested in the OP. Per the documentation:
Controls the XML schema that is generated when the attribute target is serialized by the XmlSerializer
I don't think there is a way for you to control the name of the generated array elements.
If you can however wrap the Persons collection inside another class you will then have complete control over the generated output using XmlArrayAttribute and XmlArrayItemAttribute.
If you cannot create this new class you can resort to implementing IXmlSerializable, but this is much more complex.
An example for the first alternative follows:
[XmlRoot("Context")]
public class Context
{
public Context() { this.Persons = new Persons(); }
[XmlArray("Persons")]
[XmlArrayItem("Person")]
public Persons Persons { get; set; }
}
public class Persons : List<Human> { }
public class Human
{
public Human() { }
public Human(string name) { Name = name; }
public string Name { get; set; }
}
class Program
{
public static void Main(string[] args)
{
Context ctx = new Context();
ctx.Persons.Add(new Human("john"));
ctx.Persons.Add(new Human("jane"));
var writer = new StringWriter();
new XmlSerializer(typeof(Context)).Serialize(writer, ctx);
Console.WriteLine(writer.ToString());
}
}
I had the identical problem with my serializer. None of the answers above worked exactly. I found that the XmlRoot attribute on the Human class is plainly ignored because it isn't the root element of the document. Wrapping the list in a context object wasn't an option for me because I can't change the XML schema. The solution is to change up the Persons class. Instead of subclassing a generic list, you wrap it in an object and change how it is serialized. See the sample code below:
[XmlRoot("Persons")]
public class Persons
{
public Persons ()
{
People = new List<Human>();
}
[XmlElement("Person")]
public List<Human> People
{ get; set; }
}
public class Human
{
public Human()
{
}
public Human(string name)
{
Name = name;
}
[XmlElement("Name")]
public string Name { get; set; }
}
Serializing your generic list using XmlElement means that it won't put the wrapper element around your list like XmlArray does or like the subclassing does. It also gives you the bonus option of adding attributes to the Persons class, which is where I got the idea from:
How do I add a attribute to a XmlArray element (XML Serialization)?
This is mine test code
using System.Collections.Generic;
using System.Xml.Serialization;
namespace TestLoadingMultiXml
{
[XmlRoot(ElementName=#"main")]
public class XmlMain
{
private XmlDataTest data;
[XmlElement(ElementName=#"datalist")]
public XmlDataTest Data
{
get { return data; }
set { data = value; }
} // public XmlDataTest Data
public XmlMain()
{
data = new XmlDataTest();
}
}
[XmlRoot(ElementName=#"xmldata")]
public class XmlDataTest
{
private List<DataDetails> listData;
[XmlElement(ElementName=#"listdata")]
public List<DataDetails> Data
{
get { return listData; }
set { listData = value; }
}
public XmlDataTest()
{
listData = new List<DataDetails>();
for (int i = 0; i < 10; i++)
{
DataDetails d = new DataDetails(string.Format("{0}", i));
listData.Add(d);
} // for (int i=0; i < 10; i++)
} // public XmlDataTest()
} // class XmlDataTest
[XmlRoot(ElementName=#"datadetail")]
public class DataDetails
{
private string name;
[XmlAttribute(AttributeName=#"name")]
public string Name
{
get
{
return name;
}
set { name = value; }
}
public DataDetails(string _value)
{
this.name = _value;
} // public DataDetails(string _value)
public DataDetails()
{
this.name = "";
} // public DataDetails()
} // public class DataDetails
}
and running program
using System;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace TestLoadingMultiXml
{
public partial class Form1 : Form
{
private XmlMain xt;
private string xname = #"x.xml";
public Form1()
{
InitializeComponent();
this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
}
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
XmlSerializer x = new XmlSerializer(typeof(XmlMain));
FileStream fs = new FileStream(xname, FileMode.Create);
x.Serialize(fs, xt);
fs.Close();
}
private void Form1_Load(object sender, EventArgs e)
{
xt = new XmlMain();
xname = Directory.GetCurrentDirectory() + #"\" + xname;
if (File.Exists(xname))
{
XmlSerializer x = new XmlSerializer(typeof(XmlMain));
FileStream fs = new FileStream(xname, FileMode.Open);
xt = (XmlMain)x.Deserialize(fs);
fs.Close();
} // if (File.Exists(xname))
}
}
}
Also this is the result
<?xml version="1.0"?>
<main xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<datalist>
<listdata name="0" />
<listdata name="1" />
<listdata name="2" />
<listdata name="3" />
<listdata name="4" />
<listdata name="5" />
<listdata name="6" />
<listdata name="7" />
<listdata name="8" />
<listdata name="9" />
</datalist>
</main>
Set the XmlRoot on Human to:
[XmlRoot("Person")]
Sidebar:
Persons should probably be People
There is another alternative. You can always implement IXmlSerializable in collections (and any other class or struct) to fully control how items are written or read. Many of you will know that already, it's not generally preferred of course as you may end-up writing out "boiler-plate" code by hand which should really be automatic logic specified with attributes.
For collections which must match a sensible schema it's justifiable. Because the framework has a hard limitation here and the existing item type's serialization code does not have to be duplicated when done properly; i.e. Do not re-write the item serialization in the collection code, just create/call a child XmlSerializer inside your ReadXml/WriteXml implementation.
Once consequence of using IXmlSerializable is it does not allow you to apply the XmlTypeAttribute (throws a runtime error telling you only XmlRootAttribute may be used). So instead apply the XmlSchemaProviderAttribute and return the same qualified name you would have put in the XmlTypeAttribute. The old GetSchema method should return null anyway as it was only a reserved method (according to MSDN), probably because they forgot to include the ability to specify a different namespace. Personally I use the same "GetSchema" method name in my XmlSchemaProviderAttribute so it appears as a complete override next to the legacy placeholder GetSchema method.
Of course, the best solution would be if Microsoft would allow us to apply the XmlArrayItemAttribute to collection/list classes and use that in the XmlSerializer. By default it uses the XML type element name in collections, which I feel is a bug because it should be the XML root name when specified or class name when not.
When I get time I'll come back and add an example. For now take a look at the MSDN documentation examples of IXmlSerializable and XmlSchemaProviderAttribute.
http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlschemaproviderattribute(v=vs.110).aspx
If you don't have access to the source for the Human class (in which case, setting XmlRoot is not possible), you can create an XmlElementAttribute, then add it to an XmlAttributeOverride and use that when creating an instance of your XmlSerializer. See this MSDN article for more details.
I know it's an old question but I ran into the same problem and none of the solutions seems to adresse the OP's question. So here is my solution (comments are in french if you wonder) :
#region Références
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
#endregion
namespace XmlSerializationTests
{
/// <summary>
/// Représente une liste qui peut être sérialisée en XML en tant que noeud racine.
/// </summary>
/// <typeparam name="T">Type des éléments de la liste.</typeparam>
public class XmlSerializableList<T>
: List<T>, IXmlSerializable
{
#region Variables
private static readonly XmlSerializer _ItemSerializer = new XmlSerializer(typeof(T));
private static readonly string _ItemName;
private string _RootName;
#endregion
#region Méthodes
/// <summary>
/// Initialisation statique
/// </summary>
static XmlSerializableList()
{
_ItemName = (typeof(T).GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? typeof(T).Name;
}
/// <summary>
/// Obtient le nom racine.
/// </summary>
protected virtual string RootName
{
get
{
if (string.IsNullOrWhiteSpace(_RootName)) _RootName = (GetType().GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault() as XmlRootAttribute)?.ElementName ?? GetType().Name;
return _RootName;
}
}
/// <summary>
/// Obtient le nom des éléments.
/// </summary>
protected virtual string ItemName
{
get { return _ItemName; }
}
/// <summary>
/// Cette méthode est réservée et ne doit pas être utilisée.Lorsque vous implémentez l'interface IXmlSerializable, vous devez retourner la valeur null (Nothing dans Visual Basic) à partir cette méthode et, si la spécification d'un schéma personnalisé est requise, appliquez à la place <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> à la classe.
/// </summary>
/// <returns> <see cref="T:System.Xml.Schema.XmlSchema"/> qui décrit la représentation XML de l'objet qui est généré par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> et utilisé par la méthode <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>.</returns>
public XmlSchema GetSchema()
{
return null;
}
/// <summary>
/// Génère un objet à partir de sa représentation XML.
/// </summary>
/// <param name="reader"><see cref="T:System.Xml.XmlReader"/> source à partir de laquelle l'objet est désérialisé.</param>
public void ReadXml(XmlReader reader)
{
if (!reader.IsEmptyElement)
{
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
T item = (T) _ItemSerializer.Deserialize(reader);
Add(item);
}
reader.ReadEndElement();
}
else reader.ReadStartElement();
}
/// <summary>
/// Convertit un objet en sa représentation XML.
/// </summary>
/// <param name="writer"><see cref="T:System.Xml.XmlWriter"/> flux dans lequel l'objet est sérialisé.</param>
public void WriteXml(XmlWriter writer)
{
foreach (var i in this)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
_ItemSerializer.Serialize(writer, i, ns);
}
}
#endregion
}
}
And here a unit test class to demonstrate use and results :
#region Références
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endregion
namespace XmlSerializationTests
{
[TestClass]
public class XmlSerializableListTests
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Birth { get; set; }
}
[XmlRoot("color")]
public class ColorDefinition
{
[XmlElement("name")] public string Name { get; set; }
[XmlElement("r")] public int Red { get; set; }
[XmlElement("g")] public int Green { get; set; }
[XmlElement("b")] public int Blue { get; set; }
}
public class Persons : XmlSerializableList<Person>
{
}
[XmlRoot("colors")]
public class ColorList : XmlSerializableList<ColorDefinition>
{
}
private T ReadXml<T>(string text) where T : class
{
XmlSerializer serializer = new XmlSerializer(typeof (T));
using (StringReader sr = new StringReader(text))
{
return serializer.Deserialize(sr) as T;
}
}
private string WriteXml<T>(T data) where T : class
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
serializer.Serialize(sw, data);
return sb.ToString();
}
}
[TestMethod]
public void ReadEmpty()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
</XmlSerializableListOfInt32>";
XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
Assert.AreEqual(0, lst.Count);
}
[TestMethod]
public void ReadEmpty2()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
Assert.AreEqual(0, lst.Count);
}
[TestMethod]
public void ReadSimpleItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
<int>0</int>
<int>52</int>
<int>79</int>
</XmlSerializableListOfInt32>";
XmlSerializableList<int> lst = ReadXml<XmlSerializableList<int>>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual(0, lst[0]);
Assert.AreEqual(52, lst[1]);
Assert.AreEqual(79, lst[2]);
}
[TestMethod]
public void ReadComplexItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</XmlSerializableListOfPerson>";
XmlSerializableList<Person> lst = ReadXml<XmlSerializableList<Person>>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual("Linus", lst[0].FirstName);
Assert.AreEqual("Torvalds", lst[0].LastName);
Assert.AreEqual(1969, lst[0].Birth);
Assert.AreEqual("Bill", lst[1].FirstName);
Assert.AreEqual("Gates", lst[1].LastName);
Assert.AreEqual(1955, lst[1].Birth);
Assert.AreEqual("Steve", lst[2].FirstName);
Assert.AreEqual("Jobs", lst[2].LastName);
Assert.AreEqual(1955, lst[2].Birth);
}
[TestMethod]
public void ReadInheritedPersons()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</Persons>";
Persons lst = ReadXml<Persons>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual("Linus", lst[0].FirstName);
Assert.AreEqual("Torvalds", lst[0].LastName);
Assert.AreEqual(1969, lst[0].Birth);
Assert.AreEqual("Bill", lst[1].FirstName);
Assert.AreEqual("Gates", lst[1].LastName);
Assert.AreEqual(1955, lst[1].Birth);
Assert.AreEqual("Steve", lst[2].FirstName);
Assert.AreEqual("Jobs", lst[2].LastName);
Assert.AreEqual(1955, lst[2].Birth);
}
[TestMethod]
public void ReadInheritedColors()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
<color>
<name>red</name>
<r>255</r>
<g>0</g>
<b>0</b>
</color>
<color>
<name>green</name>
<r>0</r>
<g>255</g>
<b>0</b>
</color>
<color>
<name>yellow</name>
<r>255</r>
<g>255</g>
<b>0</b>
</color>
</colors>";
ColorList lst = ReadXml<ColorList>(xml);
Assert.AreEqual(3, lst.Count);
Assert.AreEqual("red", lst[0].Name);
Assert.AreEqual(255, lst[0].Red);
Assert.AreEqual(0, lst[0].Green);
Assert.AreEqual(0, lst[0].Blue);
Assert.AreEqual("green", lst[1].Name);
Assert.AreEqual(0, lst[1].Red);
Assert.AreEqual(255, lst[1].Green);
Assert.AreEqual(0, lst[1].Blue);
Assert.AreEqual("yellow", lst[2].Name);
Assert.AreEqual(255, lst[2].Red);
Assert.AreEqual(255, lst[2].Green);
Assert.AreEqual(0, lst[2].Blue);
}
[TestMethod]
public void WriteEmpty()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32 />";
XmlSerializableList<int> lst = new XmlSerializableList<int>();
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteSimpleItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfInt32>
<int>0</int>
<int>52</int>
<int>79</int>
</XmlSerializableListOfInt32>";
XmlSerializableList<int> lst = new XmlSerializableList<int>() {0, 52, 79};
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteComplexItems()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<XmlSerializableListOfPerson>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</XmlSerializableListOfPerson>";
XmlSerializableList<Person> persons = new XmlSerializableList<Person>
{
new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
};
string result = WriteXml(persons);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteInheritedPersons()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<Persons>
<Person>
<FirstName>Linus</FirstName>
<LastName>Torvalds</LastName>
<Birth>1969</Birth>
</Person>
<Person>
<FirstName>Bill</FirstName>
<LastName>Gates</LastName>
<Birth>1955</Birth>
</Person>
<Person>
<FirstName>Steve</FirstName>
<LastName>Jobs</LastName>
<Birth>1955</Birth>
</Person>
</Persons>";
Persons lst = new Persons
{
new Person {FirstName = "Linus", LastName = "Torvalds", Birth = 1969},
new Person {FirstName = "Bill", LastName = "Gates", Birth = 1955},
new Person {FirstName = "Steve", LastName = "Jobs", Birth = 1955}
};
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
[TestMethod]
public void WriteInheritedColors()
{
string xml = #"<?xml version=""1.0"" encoding=""utf-16""?>
<colors>
<color>
<name>red</name>
<r>255</r>
<g>0</g>
<b>0</b>
</color>
<color>
<name>green</name>
<r>0</r>
<g>255</g>
<b>0</b>
</color>
<color>
<name>yellow</name>
<r>255</r>
<g>255</g>
<b>0</b>
</color>
</colors>";
ColorList lst = new ColorList
{
new ColorDefinition { Name = "red", Red = 255, Green = 0, Blue = 0 },
new ColorDefinition { Name = "green", Red = 0, Green = 255, Blue = 0 },
new ColorDefinition { Name = "yellow", Red = 255, Green = 255, Blue = 0 }
};
string result = WriteXml(lst);
Assert.AreEqual(xml, result);
}
}
}
I know it's old but for others that will come -
Before class Human, add [XmlType("Person")] instead of [XmlRoot("Person")]

Categories