Пользовательская сериализация словаря завершается неудачно, когда в xml есть отступ / разрыв строки

Чтобы иметь более чистый 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
}

1 ответ

Решение

Ваша проблема в том, что 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 элемент, таким образом разрушая будущие элементы - легко сделать ошибку при реализации IXmlSerializable,

  • Проверяя для reader.NodeType == XmlNodeType.Element && reader.LocalName == XmlElementName Я игнорирую неожиданные типы узлов, такие как комментарии.

Рабочая .Net скрипка.

Другие вопросы по тегам