问题
In order to have a cleaner XML of a Dictionary serialization, I wrote a custom class, that implements IXmlSerializable
.
My custom class is defined like this:
public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
private const string XmlElementName = "MyData";
private const string XmlAttributeId = "Id";
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
while (reader.Read())
{
if(reader.LocalName == XmlElementName)
{
var tag = reader.GetAttribute(XmlAttributeId);
var content = reader.ReadElementContentAsString();
this.Add(tag, content);
}
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (string key in this.Keys)
{
writer.WriteStartElement(XmlElementName);
writer.WriteAttributeString(XmlAttributeId, key);
writer.WriteString(this[key]);
writer.WriteEndElement();
}
}
}
My code works with this XML snippet:
<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<MyData Id="1">some content</MyData>
<MyData Id="2">some other content</MyData>
</MyCollection>
However, when I have this minified XML, my code throws an exception:
<MyCollection xmlns="http://schemas.datacontract.org/2004/07/MyProject" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><MyData Id="1">some content </MyData><MyData Id="2">some other content</MyData></MyCollection>
The exception is:
System.InvalidOperationException: The ReadElementContentAsString method is not supported on node type EndElement
It's thrown on the call to ReadElementContentAsString
.
How to fix my code?
I can repro the issue using :
var xml = @"<MyCollection xmlns=""http://schemas.datacontract.org/2004/07/MyProject"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""><MyData Id=""1"">some content </MyData><MyData Id=""2"">some other content</MyData></MyCollection>";
var raw = Encoding.UTF8.GetBytes(xml);
var serializer = new DataContractSerializer(typeof(MyCollection));
using (var ms = new MemoryStream(raw))
{
var result = serializer.ReadObject(ms); // Exception throws here
}
回答1:
Your problem is that reader.ReadElementContentAsString() positions the reader at the beginning of the next node, not the end of the current node. Then, your subsequent unconditional call to reader.Read() consumes that next node. When that node is whitespace no harm is done, but when the node is an element, the element is skipped.
The following version of your MyCollection
fixes this problem:
public class MyCollection : System.Collections.Generic.Dictionary<string, string>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
using (var subReader = reader.ReadSubtree())
{
XmlKeyValueListHelper.ReadKeyValueXml(subReader, this);
}
// Consume the EndElement also (or move past the current element if reader.IsEmptyElement).
reader.Read();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlKeyValueListHelper.WriteKeyValueXml(writer, this);
}
}
public static class XmlKeyValueListHelper
{
private const string XmlElementName = "MyData";
private const string XmlAttributeId = "Id";
public static void WriteKeyValueXml(System.Xml.XmlWriter writer, ICollection<KeyValuePair<string, string>> collection)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlElementName);
writer.WriteAttributeString(XmlAttributeId, pair.Key);
writer.WriteString(pair.Value);
writer.WriteEndElement();
}
}
public static void ReadKeyValueXml(System.Xml.XmlReader reader, ICollection<KeyValuePair<string, string>> collection)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName)
{
var tag = reader.GetAttribute(XmlAttributeId);
string content;
if (reader.IsEmptyElement)
{
content = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
content = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string, string>(tag, content));
}
else
{
// For instance a comment.
reader.Skip();
}
}
// Move past the end of the list element
reader.ReadEndElement();
}
}
Some notes:
By using XmlReader.ReadSubtree() I ensure that
ReadXml()
does not read past the end of theMyCollection
element thus corrupting future elements -- an easy mistake to make when implementingIXmlSerializable
.By checking for
reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName
I ignore unexpected types of nodes such as comments.
Working .Net fiddle.
来源:https://stackoverflow.com/questions/45844241/custom-serialization-of-a-dictionary-fails-when-xml-has-indentation-line-break