You are here: Foswiki>Main Web>NetXmlSerialization (02 Feb 2018, Sergey)EditAttach

Сериализация, XML и .NET

Введение

Рождение звезды

С выходом первой версии .NET в ней был реализован механизм сериализации, который до сих пор присутсвует и используется. Механизм был предназначен для прозрачного маршалинга типов при использовании .NET Remoting, а также была возможность использовать этот механизм отдельно. Как можно заметить, тут ни слова не было сказано об XML и это совершенно справедливо, разработчики механизма сериализации построили абстрактную модель, основанную на специальной сущности Formatter, которая отвечает за запись сериализуемого графа объектов в транспорт из которого он снова будет восстановлен. По сути, этот механизм был позаимствован из COM и сделан еще более универсальным. В нем было предусмотрено все:
  • ручная сераилизация,
  • обработка циклических ссылок,
  • запись информации о типах в транспорт,
  • механизм замены информации о типах при десериализации на случай различающихся версий сохраненной и восстанавливаемой моделей.

Как такое классное может быть таким... БЕСПЛАТНЫМ?! -- KFC

Хотя данный механизм и позволял реализовать гипотетическую сериализацию всего в этом мире, он добавлял ограничения и сложности
  • требование от разработчика понимания внутренних механизмов .NET, чем изощреннее сериализация - тем глубже нужно знать .NET и транспорты
  • сериализация подразумевалась как универсальный механизм, но каждый транспорт накладывает свои ограничения, поэтому все равно модель передачи данных (Data Tranfser Objects) требует отдельной разработки.
  • нарушение принципов ООП, например, при десериализации циклического графа сначала создаются сурогаты - т.е. объекты для которых не вызывался ни один из конструкторов (!), затем напрямую записывается их состояние,
  • требование к уровню доверия кода.

Как следствие этот механизм сейчас используется только внутри самой .NET для реализации удаленных вызовов, а простому разработчику до этого нет дела.

Эврика!

Как водится, что-то очень классное - слишком хорошо, чтобы его использовать в реальной жизни, именно так и случилось и с механизмом сериализации - он оказался слишком сложным для решения простых и малобюджетных задач неквалифицированными разработчиками. Для создания простых веб-сервисов часто начинали писать либо ручное формирование XML/JSON документов, либо использовали Reflection для некоторой автоматизации этого процесса, постепенно это вырастало в некоторые самописные фреймворки, так и появился WCF.

Создание WCF дало человечеству следующие положительные моменты:
  • очень быстрый старт - простое приложение за 5 минут и куча готовых примеров в интернете, глубокое изучение всей платформы за 3-5 дней;
  • отказ от универсальной универсальности в пользу разумной необходимости позволяет быстро создавать модели DTO для нужных транспортов без влезания во внутренности платформы;
  • поддержка форматов и протоколов транспорта стала лучше, а сам процесс сериализации более прямолинейным и контролируемым;
  • улучшилась производительность по сравнению со старыми сериализаторами.

Плюсы и минусы самой платформы WCF можно перечислять очень долго, но наша цель - это разобраться с сериализацией, которая является центральной (с точки зрения разработчика) частью сервисов.

Сериалазция WCF

В WCF нет разделения между механизмом сериализации и транспортом через который будут передаваться данные да и в принципе нет такого понятия как механизм сериализации, каждый вариант сериализации реализуется отдельным классом:
  • XmlSerializer - сериализация в XML
  • DataContractSerializer - сериализация в XML
  • NetDataContractSerializer - сериализация в XML
  • DataContractJsonSerializer - сериализация в JSON

В WCF также вводится такое понятие как DataContract - по сути это модель для передачи данных через транспорт (Data Transfer Objects) в паре с OperationContract они позволяют описать сервис, который можно будет вызывать или публиковать через WCF, но это уже другая история, нас интересует именно аспект сериализации.

Сериализатор Тип сериализуемых объектов Контроль над процессом сериализации Особенности
XmlSerializer Любые Практически полный, c использованием Xml***Attribute Не самый быстрый
DataContractSerializer Только DataContract Никакого Быстрый, документ формируется по определенным правилам
NetDataContractSerializer Только DataContract Никакого Быстрый, поддерживает циклические ссылки, пишет информацию о типах

XML

С точки зрения сериализации в XML интерес представляет собой только XmlSerializer, поскольку именно он позволяет контролировать структуру документа.

Схема документа и классы для передачи информации

XmlSerializer создается для объектов определенного типа, для этого он использует Reflection и генерирует строго-типизированный сериализатор, которому уже делегирует всю работу. Генерация занимает время и требует FullTrust уровня, что не всегда удобно, но хорошая новость в том, что можно сгенерировать строготипизированные сериализаторы на этапе сборки проекта.

Инструмент создания XML-сериализатора (sgen.exe)

Создает сборку со строготипизированными сериализаторами.

https://docs.microsoft.com/ru-ru/dotnet/standard/serialization/xml-serializer-generator-tool-sgen-exe

XML Schema Definition Tool (xsd.exe)

Позволяет выполнять различные действия со схемой документа:
  • Сборка .dll -> схема .xsd
  • Схема .xsd -> исходный код модели данных
https://docs.microsoft.com/ru-ru/dotnet/standard/serialization/xml-schema-definition-tool-xsd-exe

Schema first
Вариант использования применим, если:
  • XSD схема уже дана, например, сторонний сервис,
  • Начинаем разработку с создания примеров/тестовых данных и затем описываем создаем схему.
Используем XML Schema Definition Tool для создания классов модели данных.

Code first
Вариант использования, когда мы разрабатываем модель передаваемых данных и схему документа, при этом к последней не предъявляется требований.
  1. Создаем описание модели данных средствами языка
  2. Создаем сборку
  3. Создаем схему
Строго говоря схема здесь не обязательна, она требуется для описания протокола работы с сервисом и передачи клиентам, для работы самого сервиса она не требуется. Созданная схема может быть доработана без потери совместимости, а также использоваться как дополнительная проверка входных данных сервисом.

FAQ

Как сериализовать/десериализовать объект

    var stream = new MemoryStream();

    // сериализатор создается для определенного типа  
    var serializer = new XmlSerializer(myObj.GetType());

    serializer.Serialize(stream, myObj);
    var clone = serializer.Deserialize(stream);

Как сериализовать не в поток, а в XmlDocument

Чтение/запись XML документа требует дополнительных ресурсов (хотя и не очень больших), но иногда, сразу после сериализации с результирующим документом требуется выполнить некоторые действия, например, дописать атрибуты, требуемые транспортом, но не имеющие отношение к модели.

    var obj = new MyClass();
    
    // создаем документ
    var doc = new XmlDocument();
    
    // создаем пишущий навигатор, который будет добавлять элементы сразу в
    // документ. Пишущий навигатор можно создать не только для документа, но и
    // для произвольного XmlElement содержимое которого требуется сформировать.
    using (var writer = doc.CreateNavigator().AppendChild()) {
        var serializer = new XmlSerializer(obj.GetType());
        
        // сериализуем документ в пишущий навигатор
        serializer.Serialize(writer, obj);
    }

Обратная операция, когда уже есть узел XML документа
    XmlNode node; // может быть как узел так и документ
    MyClass obj = null;
    
    using (var reader = node.CreateNavigator().ReadSubtree()) {
        // создаем сериализатор для MyClass
        var serializer = new XmlSerializer(typeof(MyClass));
        
        obj = (MyClass)serializer.Deserialize(reader);
    }

Как десериализовать документ в котором могут быть переданы разные типы

Совсем произвольный документ десериализовать не получится, в любом случае требуется знать перечень допустимых типов которые могут быть переданы.

Создаем обертку в которой перечислим все возможные типы, она будет выступать в роли контейнера только при десериализации:
public class Exchange {
    [XmlElement("Car", typeof(Car))]
    [XmlElement("Meal", typeof(Meal))]
    [XmlElement("Pet", typeof(Pet))]
    public object Content { get; set; }
}

Для сериализации можно использовать стандартный способ
    var stream = new MemoryStream();

    // сериализатор создается для определенного типа  
    var serializer = new XmlSerializer(obj.GetType());
    serializer.Serialize(stream, myObj);

Для десериализации требуется сначала сформировать документ с элементом Exchange, загрузить в него входной документ, затем десериализовать модель.
    Stream stream; // поток с входным документом
    // создаем документ
    var doc = new XmlDocument();
    
    // создаем пишущий навигатор, который будет добавлять элементы сразу в
    // документ.
    using (var writer = doc.CreateNavigator().AppendChild())
    using(var reader = XmlReader.Create(stream)) {
        reader.MoveToContent(); // встаем на корневой элемент
        
        // создаем элемент-контейнер Exchange
        writer.WriteStartElement(nameof(Exchange));
        
        // загружаем входной документ дочерним узлом в Exchange
        writer.WriteNode(reader, true);
        
        writer.WriteEndElement();
    }
    
    // модель, которую нужно прочитать, ее тип неизвестен.
    object obj = null;
    
    // создаем читающий навигатор для документа
    using (var reader = doc.CreateNavigator().ReadSubtree()) {
        // создаем сериализатор для Exchange
        var serializer = new XmlSerializer(typeof(Exchange));
        
        // получаем переданный объект
        obj = ((Exchange)serializer.Deserialize(reader)).Content;
    }
    
    Console.WriteLine("Got {0}", obj);

Этот пример можно улучшить, написав собственный XmlReader, который будет автоматически оборачивать документы в узел Exchange:

    Stream stream; // поток с входным документом
    // создаем документ

    // модель, которую нужно прочитать, ее тип неизвестен.
    object obj = null;

    // создаем XmlReader для входного документа
    using(var reader = XmlReader.Create(stream)) 
    // создаем оборачивающий WrappingXmlReader из исходного
    using(var readerEx = new WrappingXmlReader(reader)) { 
        // создаем сериализатор для Exchange
        var serializer = new XmlSerializer(typeof(Exchange));
        
        // получаем переданный объект
        obj = ((Exchange)serializer.Deserialize(reader)).Content;
    }
Создание WrappingXmlReader более эффективный способ, но он требует дополнительных затрат.
Topic revision: r1 - 02 Feb 2018, Sergey
 
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback