为了拥有一个更简洁的Dictionary序列化XML,我编写了一个实现IXmlSerializable
的自定义类。
我的自定义类定义如下:
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();
}
}
}
我的代码适用于此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>
但是,当我有这个缩小的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>
例外是:
System.InvalidOperationException: The ReadElementContentAsString method is not supported on node type EndElement
调用ReadElementContentAsString
。
如何修复我的代码?
我可以使用以下方式重现问题:
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
}
答案 0 :(得分:2)
您的问题是reader.ReadElementContentAsString()
将读者置于下一个节点的开头,而不是当前节点的末尾。然后,您随后对reader.Read()
的无条件调用将消耗该下一个节点。当该节点是空白时,不会造成任何伤害,但当节点是一个元素时,该元素将被跳过。
以下版本的MyCollection
修复了此问题:
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();
}
}
一些注意事项:
使用XmlReader.ReadSubtree()
我确保ReadXml()
不会读取MyCollection
元素的末尾,从而破坏未来的元素 - 实施时很容易犯错.Net fiddle 1}}。
通过检查IXmlSerializable
我忽略意外类型的节点,例如评论。
工作jsfiddle。