Why the XmlSerializer aborts reading when using his reader?
Sample XML - here - .
Download sample project - here - .
When using the XmlSerializer
the class can be serialized and deserialized.
var serializer = new XmlSerializer(typeof(MainItem));
using (var reader = new StreamReader(SettingFile.FullName))
{
var deserializedObject = serializer.Deserialize(reader);
ret = (MainItem)deserializedObject;
}
Model to serialize
public class MainItem
{
public List<Child1> Child1{ get; set; }
}
In the ConnectionModel
I have one custom class where I grab the XmlReader from XmlSerializer to deserialize on custom way. This is done by implementing interface IXmlSerializable
to my class.
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read(); // <-- as soon as I comment this out, the serializer will finish proper!
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement(nameof(PropertyInfo.Name));
writer.WriteString(Value.Name);
writer.WriteEndElement();
writer.WriteStartElement(nameof(PropertyInfo.PropertyType));
writer.WriteString(Value.PropertyType.Name);
writer.WriteEndElement();
writer.WriteStartElement(XML_PropertyTypeFullName);
writer.WriteString(Value.PropertyType.FullName);
writer.WriteEndElement();
writer.WriteStartElement(nameof(PropertyInfo.CanRead));
writer.WriteString(Value.CanRead.ToString());
writer.WriteEndElement();
writer.WriteStartElement(nameof(PropertyInfo.CanWrite));
writer.WriteString(Value.CanWrite.ToString());
writer.WriteEndElement();
}
reader.Read()
will not throw any exception at all. And it reads the values correctly into the model but for testing, I commented all out, except this one line of reader.Read()
. It shows ?XmlTextReader(reader).LineNumber
in debug Window with middle of the file, not EOF. If reader.Read()
is in use (not commented out), it will read one single item instead of the next items.
Is there anything to focus on when using System.Xml.Serialization.XmlSerializer
when using IXmlSerializable
combined?
After downloading your project, I was able to create something approaching a Minimal, Complete and Verifiable example here: https://dotnetfiddle.net/OvPQ6J . While no exception is thrown, large chunks of the XML file are skipped causing the <ChildItems>
collection to be missing entries.
The problem is that your ReadXml()
is not advancing the XmlReader
past the end of the corresponding element as required in the documentation (emphasis added):
The ReadXml method must reconstitute your object using the information that was written by the
WriteXml
method.When this method is called, the reader is positioned on the start tag that wraps the information for your type. That is, directly on the start tag that indicates the beginning of a serialized object. When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the
WriteXml
method, the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.
Thus unexpected, random exceptions are a documented possible consequence of incorrectly implementing ReadXml()
. For more, see Proper way to implement IXmlSerializable? and How to Implement IXmlSerializable Correctly .
Since it is so easy to make this mistake, you can systematically avoid it by either bracketing your XML reading logic in a call to ReadSubtree()
or loading the entire XML into memory with XNode.ReadFrom()
, eg using one of the following two base classes:
public abstract class StreamingXmlSerializableBase : IXmlSerializable
{
// Populate the object with the XmlReader returned by ReadSubtree
protected abstract void Populate(XmlReader reader);
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
// Consume all child nodes of the current element using ReadSubtree()
using (var subReader = reader.ReadSubtree())
{
subReader.MoveToContent();
Populate(subReader);
}
reader.Read(); // Consume the end element itself.
}
public abstract void WriteXml(XmlWriter writer);
}
public abstract class XmlSerializableBase : IXmlSerializable
{
// Populate the object with an XElement loaded from the XmlReader for the current node
protected abstract void Populate(XElement element);
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
var element = (XElement)XNode.ReadFrom(reader);
Populate(element);
}
public abstract void WriteXml(XmlWriter writer);
}
And here's a fixed version of your SerializableClass
which uses XmlSerializableBase
:
public class SerializableClass : XmlSerializableBase
{
public string Title { get; set; } = "Test title";
public string Description { get; set; } = "Super description";
public int Number { get; set; } = (int)(DateTime.Now.Ticks % 99);
protected override void Populate(XElement element)
{
this.Title = (string)element.Element(nameof(this.Title));
this.Description = (string)element.Element(nameof(this.Description));
// Leave Number unchanged if not present in the XML
this.Number = (int?)element.Element(nameof(this.Number)) ?? this.Number;
}
public override void WriteXml(XmlWriter writer)
{
writer.WriteStartElement(nameof(this.Title));
writer.WriteString(this.Title);
writer.WriteEndElement();
writer.WriteStartElement(nameof(this.Description));
writer.WriteString(this.Description);
writer.WriteEndElement();
writer.WriteStartElement(nameof(this.Number));
// Do not use ToString() as it is locale-dependent.
// Instead use XmlConvert.ToString(), or just writer.WriteValue
writer.WriteValue(this.Number);
writer.WriteEndElement();
}
}
Notes:
In your original code you write the integer Number
to the XML as a string using ToString()
:
writer.WriteString(this.Number.ToString());
This can cause problems as the return of ToString()
can be locale-dependent. Instead use XmlConvert.ToString(Int32)
or just XmlWriter.WriteValue(Int32)
.
XmlReader.ReadSubtree()
leaves the XmlReader
positioned on the EndElement
node of the element being read, while XNode.ReadFrom()
leaves the reader positioned immediately after the EndElement
node of the element being read. This accounts for the extra call to Read()
in StreamingXmlSerializableBase.ReadXml()
.
Code that manually reads XML using XmlReader
should always be unit-tested with both formatted and unformatted XML, because certain bugs will only arise with one or the other. (See eg this answer and this one also for examples of such.)
Sample working .Net fiddle here: https://dotnetfiddle.net/s9OJOQ .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.