XmlSerializer прерывает чтение после читателя. Read () - PullRequest
0 голосов
/ 26 апреля 2018

Почему XmlSerializer прерывает чтение при использовании своего ридера? enter image description here

Пример XML - здесь - .
Скачать пример проекта - здесь - .

При использовании XmlSerializer класс можно сериализовать и десериализовать.

var serializer = new XmlSerializer(typeof(MainItem));
using (var reader = new StreamReader(SettingFile.FullName))
{
    var deserializedObject = serializer.Deserialize(reader);
    ret = (MainItem)deserializedObject;
}

Модель для сериализации

public class MainItem
{
    public List<Child1> Child1{ get; set; }
}

В ConnectionModel у меня есть один пользовательский класс, где я извлекаю XmlReader из XmlSerializer для десериализации по собственному усмотрению. Это делается путем реализации интерфейса IXmlSerializable для моего класса.

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() не будет вообще не выдавать исключение . И он правильно считывает значения в модель, но для тестирования я закомментировал все, кроме этой одной строки reader.Read(). Он показывает ?XmlTextReader(reader).LineNumber в окне отладки с серединой файла, а не EOF. Если reader.Read() используется (не закомментировано), он будет читать один элемент вместо следующих элементов.

Есть ли на что обратить внимание при использовании System.Xml.Serialization.XmlSerializer при использовании IXmlSerializable в сочетании?

1 Ответ

0 голосов
/ 27 апреля 2018

После загрузки вашего проекта я смог создать что-то похожее на пример Minimal, Complete и Verifiable здесь: https://dotnetfiddle.net/OvPQ6J. Хотя исключение не выдается, большие куски XML-файла пропускаются в результате чего в коллекции <ChildItems> отсутствуют записи.

Проблема в том, что ваш ReadXml() не продвигается XmlReader после конца соответствующего элемента, как требуется в документации (выделение добавлено):

Метод ReadXml должен воссоздать ваш объект, используя информацию, записанную методом WriteXml.

Когда вызывается этот метод, читатель помещается в начальный тег, который оборачивает информацию для вашего типа. То есть непосредственно на начальном теге, который указывает начало сериализованного объекта. Когда этот метод возвращается, он должен прочитать весь элемент от начала до конца, включая все его содержимое. В отличие от метода WriteXml, каркас не обрабатывает элемент-оболочку автоматически. Ваша реализация должна сделать это. Несоблюдение этих правил позиционирования может привести к тому, что код сгенерирует непредвиденные исключения во время выполнения или поврежденные данные.

Таким образом, неожиданные, случайные исключения являются документированным возможным следствием неправильной реализации ReadXml(). Для получения дополнительной информации см. Правильный способ реализации IXmlSerializable? и Как правильно реализовать IXmlSerializable .

Поскольку эту ошибку легко совершить, ее можно систематически избежать, либо заключив в скобки логику чтения XML при вызове ReadSubtree() или загрузив весь XML в память с помощью XNode.ReadFrom(), например используя один из следующих двух базовых классов:

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);
}

А вот исправленная версия вашего SerializableClass, которая использует 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();
    }
}

Примечания:

  • В исходном коде вы пишете целое число Number в XML в виде строки, используя ToString():

    writer.WriteString(this.Number.ToString());  
    

    Это может вызвать проблемы, так как возвращение ToString() может зависеть от локали. Вместо этого используйте XmlConvert.ToString(Int32) или просто XmlWriter.WriteValue(Int32).

  • XmlReader.ReadSubtree() оставляет XmlReader с позицией на EndElement узле читаемого элемента, тогда как XNode.ReadFrom() оставляет считыватель с позицией сразу после EndElement узел читаемого элемента. Это составляет дополнительный вызов Read() в StreamingXmlSerializableBase.ReadXml().

  • Код, который вручную читает XML с использованием XmlReader, всегда должен подвергаться модульному тестированию как с отформатированным, так и с неформатированным XML, поскольку определенные ошибки могут возникать только с одним или другим. (См., Например, этот ответ и этот ответ также для примеров такого.)

Пример рабочей .Net скрипки здесь: https://dotnetfiddle.net/s9OJOQ.

...