Custom class with IXmlSerializable fails with OutOfMemoryException - c#

I have the following xml file:
<MyConfig>
<Item a1="Attrib11" a2="Attrib21" a3="Attrib31" />
<Item a1="Attrib12" a2="Attrib22" a3="Attrib32" />
</MyConfig>
I load it in using the following helper methods:
public static T Load<T>(string path)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
using (StreamReader sr = new StreamReader(fs))
{
return (T)xml.Deserialize(sr);
}
}
public static void Save<T>(string path, T contents)
{
XmlSerializer xml = new XmlSerializer(typeof(T));
using (FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
using (StreamWriter sw = new StreamWriter(fs))
{
xml.Serialize(sw, contents, ns);
}
}
This is MyConfig:
public class MyConfig
{
[XmlElement("Item")]
public List<Item> Items { get; set; }
public MyConfig()
{
Items = new List<Item>();
}
}
public class Item : IXmlSerializable
{
[XmlAttribute()]
public string Attrib1 { get; set; }
[XmlAttribute()]
public string Attrib2 { get; set; }
[XmlAttribute()]
public string Attrib3 { get; set; }
public Item(string attrib1, string attrib2, string attrib3)
{
Attrib1 = attrib1;
Attrib2 = attrib2;
Attrib3 = attrib3;
}
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element)
{
Attrib1 = reader.GetAttribute("a1");
Attrib2 = reader.GetAttribute("a2");
Attrib3 = reader.GetAttribute("a3");
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("a1", Attrib1);
writer.WriteAttributeString("a2", Attrib2);
writer.WriteAttributeString("a3", Attrib3);
}
}
I then have the following test bed code for checking the serialization of the class:
string file = "somePath";
MyConfig myConfig = new MyConfig()
{
Items = new List<Item>()
{
new Item("Attrib11", "Attrib21", "Attrib31"),
new Item("Attrib12", "Attrib22", "Attrib32"),
},
};
Save(file, myConfig);
MyConfig myConfig2 = Load<MyConfig>(file);
This fails with an OutOfMemory exception at Load, how can I fix this? Checking the file at somePath and it looks correct.

You need to tell the reader to advance to the next node after you've read the attributes:
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element)
{
Attrib1 = reader.GetAttribute("a1");
Attrib2 = reader.GetAttribute("a2");
Attrib3 = reader.GetAttribute("a3");
}
// Go to the next node.
reader.Read();
}
If you don't call reader.Read(), the reader reads the same node over and over again, and therefore the XmlSerializer will create an unlimited amount of Item instances until you finally get the OutOfMemoryException.

Related

Serialize and De-serialize XML with commented sections in C#

I was wondering that how i can keep the commented part in XML (After serialization).
Is there any way to do so?
Here is my problem,
I have XML file with lots of nodes. My .NET application will load the XML file and serialize into C# class. Then will change some nodes (by BL) in the class and de-serialize and save the file again.
After saving, the comments i kept on some nodes are disappeared.
Is there is any way to avoid resetting of XML comments using C# ?
Thanks in advance
Suppose an xml like this
<?xml version="1.0" encoding="utf-8"?>
<Test>
<!--Foo Description!-->
<Foo>FooText</Foo>
<!--Bar Description!-->
<Bar>BarText</Bar>
</Test>
var xml = GenericSerializator<Test>.LoadObjectFromFile("test.xml");
xml.Foo += "1";
xml.FooCommnet += "2";
xml.Bar += "3";
xml.BarCommnet += "4";
GenericSerializator<Test>.SaveObjectToFile(xml, "test2.xml");
<?xml version="1.0" encoding="utf-16"?>
<Test>
<!--Foo Description!2-->
<Foo>FooText1</Foo>
<!--Bar Description!4-->
<Bar>BarText3</Bar>
</Test>
we can do it using this code:
internal static class GenericSerializator<T> where T : class
{
public static T LoadObjectFromFile(string fileName)
{
using (var file = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
var xmlSerializer = new XmlSerializer(typeof(T));
return (T)xmlSerializer.Deserialize(file);
}
}
public static void SaveObjectToFile(object value, string fileName)
{
var xmlSerializer = new XmlSerializer(typeof(T));
using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
fileStream.Seek(0, SeekOrigin.End);
using (var streamWriter = new StreamWriter(fileStream, Encoding.Unicode))
{
xmlSerializer.Serialize(streamWriter, value);
}
}
}
}
public class Test : XmlSerializableWithComments
{
[XmlIgnore, Description]
public string FooCommnet { get; set; }
public string Foo { get; set; }
[XmlIgnore, Description]
public string BarCommnet { get; set; }
public string Bar { get; set; }
}
public class XmlSerializableWithComments : IXmlSerializable
{
private PropertyInfo[] Properties { get; set; }
public XmlSerializableWithComments()
{
Properties = GetType().GetProperties();
}
public void WriteXml(XmlWriter writer)
{
foreach (var propertyInfo in Properties)
{
var value = propertyInfo.GetValue(this, null).ToString();
if (propertyInfo.IsDefined(typeof(DescriptionAttribute), false))
{
writer.WriteComment(value);
}
else
{
writer.WriteElementString(propertyInfo.Name, value);
}
}
}
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.Read();
}
string comment = null;
if (reader.NodeType == XmlNodeType.Comment)
{
comment = reader.Value;
}
reader.Read();
if (reader.NodeType == XmlNodeType.Element)
{
var propertyName = reader.LocalName;
PropertyInfo temp;
if ((temp = Properties.FirstOrDefault(i => i.Name == propertyName)) != null)
{
reader.Read();
temp.SetValue(this, reader.Value);
if (!string.IsNullOrEmpty(comment))
{
if ((temp = Properties.FirstOrDefault(i => i.Name == propertyName + "Commnet")) != null)
{
temp.SetValue(this, comment);
}
comment = null;
}
}
}
}
}
}
}

Saving a generic list

Is there a way to save a list to disk generically? I tried data contract serializer, but it always generates an empty list.
public static List<T> Load<T>() where T : class,new()
{
var serializer = new DataContractSerializer(typeof(List<T>));
string path = HttpContext.Current.Server.MapPath("~/App_Data/" + typeof(T).ToString() + ".xml");
if (!System.IO.File.Exists(path))
{
return new List<T>();
}
else
{
using (var s = new System.IO.FileStream(path, System.IO.FileMode.Open))
{
return serializer.ReadObject(s) as List<T>;
}
}
}
public static void Save<T>(List<T> data) where T : class,new()
{
var serializer = new DataContractSerializer(typeof(List<T>));
Enumerate<T>(data);
string path = HttpContext.Current.Server.MapPath("~/App_Data/" + typeof(T).ToString() + ".xml");
using (var s = new System.IO.FileStream(path, System.IO.FileMode.Create))
{
serializer.WriteObject(s, data);
}
}
You might want to use JavaScriptSerializer
var json = new JavaScriptSerializer().Serialize(thing);
JSON lists are generic.
UPDATE: Ass claimed by TimS Json.NET is better serializer so if adding 3rd party library is an option here is an article on how to do it.
What about a binary serializer
public static void SerializeToBin(object obj, string filename)
{
Directory.CreateDirectory(Path.GetDirectoryName(filename));
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
bf.Serialize(fs, obj);
}
}
public static T DeSerializeFromBin<T>(string filename) where T : new()
{
if (File.Exists(filename))
{
T ret = new T();
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
ret = (T)bf.Deserialize(fs);
}
return ret;
}
else
throw new FileNotFoundException(string.Format("file {0} does not exist", filename));
}
Based on what you're trying to do, the key might also be your class T, ensuring that it is decorated with the proper [DataContract] and [DataMember] attributes. Here is a working example based on your code in question (however if you don't care to be able to utilize the persisted file outside of your code, you might find better performance in the binary serializer):
[DataContract]
public class Mydata
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Mydata> myData = new List<Mydata>();
myData.Add(new Mydata() { Id = 1, Name = "Object 1" });
myData.Add(new Mydata() { Id = 2, Name = "Object 2" });
string path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + #"\" + typeof(Mydata).ToString() + ".xml";
Save<Mydata>(myData, path);
List<Mydata> myNewData = Load<Mydata>(path);
Console.WriteLine(myNewData.Count);
Console.ReadLine();
}
public static List<T> Load<T>(string filename) where T : class
{
var serializer = new DataContractSerializer(typeof(List<T>));
if (!System.IO.File.Exists(filename))
{
return new List<T>();
}
else
{
using (var s = new System.IO.FileStream(filename, System.IO.FileMode.Open))
{
return serializer.ReadObject(s) as List<T>;
}
}
}
public static void Save<T>(List<T> list, string filename)
{
var serializer = new DataContractSerializer(typeof(List<T>));
using (FileStream fs = new FileStream(filename, FileMode.Create))
{
using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs))
{
serializer.WriteObject(writer, list);
}
}
}
}

PaymentDetails is not successfully deserialized, returning a null object, because it seems it is expected to have IXmlDeserializable

[XmlRoot("Quote")]
public class Quote
{
[XmlElement("Insurance")]
public InsuranceDetails InsDetails { get; set; }
[XmlElement("Payment")]
public PaymentDetails PayDetails { get; set; }
}
public class InsuranceDetails : IXmlSerializable
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("Insurance");
Details1 = reader.ReadElementString("Details1");
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
// do write suff
}
}
public class PaymentDetails
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
}
Given this example, using XmlSerializer to deserialize my string to QuoteObject, PaymentDetails is not successfully deserialized, returning a null object, because it seems it is expected to have IXmlDeserializable. It only works if PaymentDetails is parsed in first place. Is this some expected behavior from XmlSerializer?
using (TextReader read = new StringReader(xml))
{
var serializer = new XmlSerializer(typeof(Quote));
return (Quote)serializer.Deserialize(read);
}
Well these are the ReadXml and WriteXml I modified:
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
var empty=reader.IsEmptyElement;
reader.ReadStartElement();
if(!empty){
Details1=reader.ReadElementString("Details1");
reader.ReadEndElement();
}
}
public void WriteXml(XmlWriter writer)
{
var str=string.IsNullOrWhiteSpace(Details1)?"":Details1;
writer.WriteElementString("Details1",str);
}
Following are serialize and deserialize functions:
public static string Serialize<T>(T t)
{
var xmlser=new XmlSerializer(typeof(T));
XmlWriterSettings settings = new XmlWriterSettings();
using(StringWriter textWriter = new StringWriter()) {
using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
xmlser.Serialize(xmlWriter, t);
}
return textWriter.ToString();
}
}
public static T Deserialize<T>(string xml)
{
if(string.IsNullOrEmpty(xml)) {
return default(T);
}
XmlSerializer serializer = new XmlSerializer(typeof(T));
XmlReaderSettings settings = new XmlReaderSettings();
using(StringReader textReader = new StringReader(xml)) {
using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
return (T) serializer.Deserialize(xmlReader);
}
}
}
Serialization Test:
var q=new Quote();
q.PayDetails = new PaymentDetails{Details1="Payment Details 1"};
q.InsDetails=new InsuranceDetails{Details1="Insurance Details 1"};
str = Serialize<Quote>(q);
Which gives (str):
<?xml version="1.0" encoding="utf-16"?>
<Quote xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Payment>
<Details1>Payment Details 1</Details1>
</Payment>
<Insurance>
<Details1>Insurance Details 1</Details1>
</Insurance>
</Quote>
Deserialization Test:
var dq=Deserialize<Quote>(str);
Console.WriteLine(dq.PaymentDetails.Detail1);//gives "Payment Details 1"
Console.WriteLine(dq.InsuranceDetails.Detail1);//gives "Insurance Details 1"
PS:- The Serialize code was copied from another SO answer verbatim. I learned how to serialize to string using StringWriter.
First of all you don't have to implement IXmlSerializable in any of the classes. Second of all, you don't provide the content of the xml variable. It may contain a mistype/bug, if you created it manually.
I used the following code, to test your classes:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace XmlDeSerialize
{
[XmlRoot("Quote")]
public class Quote
{
[XmlElement("Insurance")]
public InsuranceDetails InsDetails { get; set; }
[XmlElement("Payment")]
public PaymentDetails PayDetails { get; set; }
}
public class InsuranceDetails
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
}
public class PaymentDetails
{
[XmlElement(ElementName = "Details1")]
public string Details1 { get; set; }
}
class Program
{
static void Main(string[] args)
{
var qin = new Quote
{
InsDetails = new InsuranceDetails { Details1 = "insurance details text" },
PayDetails = new PaymentDetails { Details1 = "payment details text" },
};
string xml;
using (var stream = new MemoryStream())
{
var serializer = new XmlSerializer(typeof(Quote));
serializer.Serialize(stream, qin);
stream.Position = 0;
using (var sr = new StreamReader(stream))
{
xml = sr.ReadToEnd();
}
}
Quote qout;
using (TextReader read = new StringReader(xml))
{
var deserializer = new XmlSerializer(typeof(Quote));
var obj = deserializer.Deserialize(read);
qout = (Quote)obj;
}
Console.WriteLine("InsDetails.Details1='{0}'", qout.InsDetails.Details1);
Console.WriteLine("PayDetails.Details1='{0}'", qout.PayDetails.Details1);
}
}
}
The value of xml after serialization:
<?xml version="1.0"?>
<Quote xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Insurance>
<Details1>insurance details text</Details1>
</Insurance>
<Payment>
<Details1>payment details text</Details1>
</Payment>
</Quote>
The console output I received:
InsDetails.Details1='insurance details text'
PayDetails.Details1='payment details text'
Try the code yourself and see if it works for you. Clearly to me you don't provide valid XML content for deserialization, or other part of your code you did not provide in your question is to blame.

serialize and store object in another object that implements IXmlSerializable

I would like to XML serialize instances of my object Exception and store it in the XMLNode[] Nodes property of another object ExceptionReport.
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Xml.Serialization.XmlSchemaProviderAttribute("ExportSchema")]
[System.Xml.Serialization.XmlRootAttribute(IsNullable = false)]
public partial class ExceptionReport : object, System.Xml.Serialization.IXmlSerializable
{
public System.Xml.XmlNode[] Nodes { get; set; }
public void ReadXml(System.Xml.XmlReader reader)
{
this.Nodes = System.Runtime.Serialization.XmlSerializableServices.ReadNodes(reader);
}
public void WriteXml(System.Xml.XmlWriter writer)
{
System.Runtime.Serialization.XmlSerializableServices.WriteNodes(writer, this.Nodes);
}
}
public class Exception
{
public string ExceptionText;
public string exceptionCode;
public string locator;
}
How would i go about doing this so the result would be something like this:
<ExceptionReport xmlns="http://www.opengis.net/ows" >
<Exception exceptionCode="1">my first instance</Exception>
<Exception exceptionCode="2">my second instance</Exception>
</ExceptionReport>
So far i have the following but i need to know how to serialize these objects and store them in the ExceptionReport Nodes array.
ExceptionReport er = new ExceptionReport();
Exception exception_item1 = new Exception();
exception_item1.ExceptionText = "my first instance";
exception_item1.exceptionCode = "1";
Exception exception_item2 = new Exception();
exception_item2.ExceptionText = "my second instance";
exception_item2.exceptionCode = "2";
List<Exception> exceptions = new List<Exception>( exception_item1, exception_item2 );
[XmlRoot("ExceptionReport")]
public partial class ExceptionReport
{
[XmlElement("Exception")]
public List<Exception> Nodes { get; set; }
public ExceptionReport()
{
Nodes = new List<Exception>();
}
}
public class Exception
{
[XmlText]
public string ExceptionText;
[XmlAttribute("exceptionCode")]
public int ExceptionCode;
[XmlAttribute("locator")]
public string Locator;
}
Then to serialize, I use the following extensions:
public static bool XmlSerialize<T>(this T item, string fileName)
{
return item.XmlSerialize(fileName, true);
}
public static bool XmlSerialize<T>(this T item, string fileName, bool removeNamespaces)
{
object locker = new object();
XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
lock (locker)
{
using (XmlWriter writer = XmlWriter.Create(fileName, settings))
{
if (removeNamespaces)
{
xmlSerializer.Serialize(writer, item, xmlns);
}
else { xmlSerializer.Serialize(writer, item); }
writer.Close();
}
}
return true;
}
public static T XmlDeserialize<T>(this string s)
{
object locker = new object();
StringReader stringReader = new StringReader(s);
XmlTextReader reader = new XmlTextReader(stringReader);
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
lock (locker)
{
T item = (T)xmlSerializer.Deserialize(reader);
reader.Close();
return item;
}
}
finally
{
if (reader != null)
{ reader.Close(); }
}
}
public static T XmlDeserialize<T>(this FileInfo fileInfo)
{
string xml = string.Empty;
using (FileStream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd().XmlDeserialize<T>();
}
}
}
Use like this:
ExceptionReport report = new ExceptionReport();
report.Nodes.Add(new Exception { ExceptionText = "my first instance", ExceptionCode = 1, Locator = "loc1" });
report.Nodes.Add(new Exception { ExceptionText = "my second instance", ExceptionCode = 2 });
report.XmlSerialize("C:\\test.xml");
I tested and it came out like you wanted. Hope it helps...
PS - The extensions came from my library on codeproject: http://www.codeproject.com/KB/dotnet/MBGExtensionsLibrary.aspx

XML de-serialization using xml element/attributes

Need your help in setting the xml attributes for XML deserialization.
This is my input xml:
<form>
<question id="QnA">
<answer>AnswerforA</answer>
</question>
<question id="QnB">
<answer>AnswerforB</answer>
</question>
<question id="QnC">
<answer>AnswerforC</answer>
</question>
</form>
The ids of each question element tag correspond to a class property and its value is the innertext of the corresponding answer element.
The .cs file will look like
public class Test
{
private string qnaAns;
private string qnbAns;
private string qncAns;
public string QnA
{
get{ return qnaAns;}
set{qnaAns = value;}
}
public string QnB
{
get{ return qnbAns;}
set{qnbAns = value;}
}
public string QnC
{
get{ return qncAns;}
set{qncAns = value;}
}
}
and I use the follwing code for deserialization
XmlSerializer ser = new XmlSerializer(typeof(Test));
XmlReader xr = new xmlReader(inputxml);
Test t = ser.Deserialize(xr) as Test;
Please let me know how to set the XML element/attribute for the Test class to achieve this.
Thanks for your time.
[XmlRoot("form")]
public class Form
{
[XmlElement("question")]
public List<Question> Questions { get; set; }
public Form()
{
Questions = new List<Question>();
}
}
public struct Question
{
[XmlAttribute("id")]
public string ID { get; set; }
[XmlElement("answer")]
public string Answer { get; set; }
}
Then to serialize, I use the following extensions:
public static bool XmlSerialize<T>(this T item, string fileName)
{
return item.XmlSerialize(fileName, true);
}
public static bool XmlSerialize<T>(this T item, string fileName, bool removeNamespaces)
{
object locker = new object();
XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
lock (locker)
{
using (XmlWriter writer = XmlWriter.Create(fileName, settings))
{
if (removeNamespaces)
{
xmlSerializer.Serialize(writer, item, xmlns);
}
else { xmlSerializer.Serialize(writer, item); }
writer.Close();
}
}
return true;
}
public static T XmlDeserialize<T>(this string s)
{
object locker = new object();
StringReader stringReader = new StringReader(s);
XmlTextReader reader = new XmlTextReader(stringReader);
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
lock (locker)
{
T item = (T)xmlSerializer.Deserialize(reader);
reader.Close();
return item;
}
}
finally
{
if (reader != null)
{ reader.Close(); }
}
}
public static T XmlDeserialize<T>(this FileInfo fileInfo)
{
string xml = string.Empty;
using (FileStream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd().XmlDeserialize<T>();
}
}
}
Hope this helps.
PS - The extensions came from my library on codeproject: http://www.codeproject.com/KB/dotnet/MBGExtensionsLibrary.aspx

Categories