Использование класса XmlSerializer

Windows Communication Foundation (WCF) может использовать две различные технологии сериализации для преобразования данных в приложение в XML, передаваемых между клиентами и службами: DataContractSerializer и XmlSerializer.

DataContractSerializer

По умолчанию WCF использует DataContractSerializer класс для сериализации типов данных. Данный сериализатор поддерживает следующие типы:

  • Примитивные типы (например, целые числа, строки и массивы байтов), а также некоторые специальные типы, такие как XmlElement и DateTime, обрабатываемые как примитивы.

  • Типы контрактов данных (типы, отмеченные атрибутом DataContractAttribute).

  • Типы, отмеченные атрибутом SerializableAttribute, включающие типы, реализующие интерфейс ISerializable.

  • Типы, реализующие интерфейс IXmlSerializable.

  • Множество типов общих коллекций, включающих множество типов универсальных коллекций.

Многие платформа .NET Framework типы попадают в последние две категории и, таким образом, сериализуются. Массивы сериализуемых типов также являются сериализуемыми. Полный список см. в разделе "Указание передачи данных в контрактах служб".

Рекомендуемый DataContractSerializerспособ записи новых служб WCF, используемых вместе с типами контрактов данных. Дополнительные сведения см. в разделе "Использование контрактов данных".

XmlSerializer

WCF также поддерживает XmlSerializer класс. Класс XmlSerializer не является уникальным для WCF. Это тот же механизм сериализации, который ASP.NET используют веб-службы. Класс XmlSerializer поддерживает более узкий набор типов по сравнению с классом DataContractSerializer, но позволяет более четко контролировать получаемый XML-код и более полно поддерживает стандарт языка определения схемы XML (XSD). Кроме того, данный класс не требует никаких декларативных атрибутов на сериализуемых типах. Дополнительные сведения см. в разделе сериализации XML в документации по платформа .NET Framework. Класс XmlSerializer не поддерживает типы контрактов данных.

При использовании Svcutil.exe или функции добавления ссылки на службу в Visual Studio для создания клиентского кода для сторонней службы или доступа к сторонней схеме соответствующий сериализатор автоматически выбирается для вас. Если схема не совместима с DataContractSerializer, выбирается XmlSerializer.

Переключение на XmlSerializer

Иногда может понадобиться ручное переключение на XmlSerializer. Это происходит, например, в следующих случаях:

  • При переносе приложения из ASP.NET веб-служб в WCF может потребоваться повторно использовать существующие совместимые XmlSerializerтипы вместо создания новых типов контрактов данных.

  • Если важен четкий контроль над XML-кодом, появляющимся в сообщении, но документ WSDL отсутствует - например, при создании службы с типами, которые должны соответствовать некоторой стандартизированной опубликованной схеме, которая не совместима с DataContractSerializer.

  • При создании служб, которые используют стандарт предыдущих версий с кодировкой SOAP.

В этом и в других случаях может понадобиться ручное переключение на класс XmlSerializer путем применения атрибута XmlSerializerFormatAttribute к службе, как показано в следующем коде.

[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
[OperationContract]
    public void ProcessTransaction(BankingTransaction bt)
    {
        // Code not shown.
    }
}

//BankingTransaction is not a data contract class,
//but is an XmlSerializer-compatible class instead.
public class BankingTransaction
{
    [XmlAttribute]
    public string Operation;
    [XmlElement]
    public Account fromAccount;
    [XmlElement]
    public Account toAccount;
    [XmlElement]
    public int amount;
}
//Notice that the Account class must also be XmlSerializer-compatible.
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
    <OperationContract()> _
    Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
        ' Code not shown.
    End Sub
End Class


' BankingTransaction is not a data contract class,
' but is an XmlSerializer-compatible class instead.

Public Class BankingTransaction
    <XmlAttribute()> _
    Public Operation As String
    <XmlElement()> _
    Public fromAccount As Account
    <XmlElement()> _
    Public toAccount As Account
    <XmlElement()> _
    Public amount As Integer
End Class
'Notice that the Account class must also be XmlSerializer-compatible.

Соображения безопасности

Примечание.

При переключении модулей сериализации необходимо соблюдать меры предосторожности. Один и тот же тип может быть сериализован в XML-код по-разному, в зависимости от используемого сериализатора. Если случайно был использован не тот сериализатор, может быть раскрыта информация из типа, который раскрывать не предполагалось.

Например, класс DataContractSerializer при сериализации типов контрактов данных сериализует только элементы, отмеченные атрибутом DataMemberAttribute. Класс XmlSerializer сериализует любой открытый элемент. Смотрите тип в следующем коде.

[DataContract]
public class Customer
{
    [DataMember]
    public string firstName;
    [DataMember]
    public string lastName;
    public string creditCardNumber;
}
<DataContract()> _
Public Class Customer
    <DataMember()> _
    Public firstName As String
    <DataMember()> _
    Public lastName As String
    Public creditCardNumber As String
End Class

Если тип был случайно использован в контракте службы, где выбран класс XmlSerializer, сериализуется элемент creditCardNumber, который, судя по всему, для этого не предназначен.

Даже если класс DataContractSerializer является значением по умолчанию, можно явным образом выбрать его для своей службы (хотя делать это не всегда обязательно), применив атрибут DataContractFormatAttribute к типу контракта данных.

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

Другие важные вопросы безопасности распространяются на класс XmlSerializer. Во-первых, настоятельно рекомендуется подписать любое приложение WCF, использующее XmlSerializer класс, с ключом, защищенным от раскрытия. Данная рекомендация применяется и при переключении на XmlSerializer вручную, и при выполнении автоматического переключения (с помощью Svcutil.exe, добавления ссылки на службы или подобных средств). Это связано с тем, что XmlSerializer модуль сериализации поддерживает загрузку предварительно созданных сборок сериализации, если они подписаны с тем же ключом, что и приложение. Неподписанное приложение совершенно не защищено от возможности совпадения злонамеренной сборки с ожидаемым именем заранее созданной сборки сериализации, размещенной в папке приложения или в глобальном кэше сборок. Чтобы попытаться сделать это, злоумышленнику, конечно, сначала нужно получить доступ с правами записи к одному из этих двух расположений.

Другая угроза, которая существует при использовании XmlSerializer, относится к доступу с правами записи к временной папке системы. Модуль XmlSerializer сериализации создает и использует временные сборки сериализации в этой папке. Следует иметь в виду, что любой процесс с доступом на запись к временной папке может перезаписать эти сборки сериализации с помощью вредоносного кода.

Правила для поддержки XmlSerializer

Нельзя непосредственно применить XmlSerializer-совместимые атрибуты к параметрам операции контракта или возвратить значения. Однако они могут быть применены к типизированным сообщениям (части тела контракта сообщения), как показано в следующем коде.

[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
    [OperationContract]
    public void ProcessTransaction(BankingTransaction bt)
    {
        //Code not shown.
    }
}

[MessageContract]
public class BankingTransaction
{
    [MessageHeader]
    public string Operation;
    [XmlElement, MessageBodyMember]
    public Account fromAccount;
    [XmlElement, MessageBodyMember]
    public Account toAccount;
    [XmlAttribute, MessageBodyMember]
    public int amount;
}
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
    <OperationContract()> _
    Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
        'Code not shown.
    End Sub
End Class

<MessageContract()> _
Public Class BankingTransaction
    <MessageHeader()> _
    Public Operation As String
    <XmlElement(), MessageBodyMember()> _
    Public fromAccount As Account
    <XmlElement(), MessageBodyMember()> _
    Public toAccount As Account
    <XmlAttribute(), MessageBodyMember()> _
    Public amount As Integer
End Class

При применении к типизированным элементам сообщений данные атрибуты переопределяют свойства, конфликтующие с атрибутами типизированного сообщения. Например, в следующем коде ElementName переопределяет Name.

    [MessageContract]
    public class BankingTransaction
    {
        [MessageHeader] public string Operation;

        //This element will be <fromAcct> and not <from>:
        [XmlElement(ElementName="fromAcct"), MessageBodyMember(Name="from")]
        public Account fromAccount;

        [XmlElement, MessageBodyMember]
        public Account toAccount;

        [XmlAttribute, MessageBodyMember]
        public int amount;
}
<MessageContract()> _
Public Class BankingTransaction
    <MessageHeader()> _
    Public Operation As String

    'This element will be <fromAcct> and not <from>:
    <XmlElement(ElementName:="fromAcct"), _
        MessageBodyMember(Name:="from")> _
    Public fromAccount As Account

    <XmlElement(), MessageBodyMember()> _
    Public toAccount As Account

    <XmlAttribute(), MessageBodyMember()> _
    Public amount As Integer
End Class

Атрибут MessageHeaderArrayAttribute не поддерживается при использовании XmlSerializer.

Примечание.

В этом случае XmlSerializer вызывается следующее исключение, которое выпускается до WCF: "Элемент, объявленный на верхнем уровне схемы, не может иметь maxOccurs> 1. Укажите элемент-оболочку для more, указав XmlArray или XmlArrayItem вместо XmlElementAttribute или стиль параметров Wrapped».

При получении такого исключения, разберитесь, с такой ли ситуацией пришлось столкнуться.

WCF не поддерживает SoapIncludeAttribute и XmlIncludeAttribute атрибуты в контрактах сообщений и контрактах операций. Вместо этого используйте KnownTypeAttribute атрибут.

Типы, реализующие интерфейс IXmlSerializable

Типы, реализующие интерфейс IXmlSerializable, полностью поддерживаются сериализатором DataContractSerializer. К данным типам всегда нужно применять атрибут XmlSchemaProviderAttribute, необходимый для управления их схемой.

Предупреждение

Если выполняется сериализация полиморфных типов, необходимо применить атрибут XmlSchemaProviderAttribute к типу, чтобы убедиться, что сериализуется правильный тип.

Существует три разновидности типов, реализующих интерфейс IXmlSerializable: типы, представляющие производное содержимое; типы, представляющие одиночный элемент; устаревшие типы DataSet.

  • Типы содержимого используют метод поставщика схемы, заданный атрибутом XmlSchemaProviderAttribute. Метод не возвращает значение null, и свойство IsAny атрибута остается в значении по умолчанию false. Это наиболее распространенное использование типов IXmlSerializable.

  • Типы элемента используется в том случае, когда тип IXmlSerializable должен управлять собственным именем корневого элемента. Чтобы пометить тип как тип элемента, установите свойство IsAny атрибута XmlSchemaProviderAttribute в значение true или верните значение null из метода поставщика схемы. Наличие метода поставщика схемы является необязательным для типов элементов - вместо имени метода в null можно указать значение XmlSchemaProviderAttribute. Однако если IsAny имеет значение true и указан метод поставщика схемы, то метод должен возвращать значение null.

  • Устаревшие типы DataSet являются типами IXmlSerializable, не отмеченными атрибутом XmlSchemaProviderAttribute. Вместо этого они используют для создания схемы метод GetSchema. Данный шаблон используется для типа DataSet, и его типизированный набор данных наследует класс в более ранних версиях .NET Framework; в настоящее время он устарел и поддерживается только из соображений совместимости с более ранними версиями. Не используйте данный шаблон и всегда применяйте атрибут XmlSchemaProviderAttribute к своим типам IXmlSerializable.

Типы содержимого IXmlSerializable

При сериализации элемента данных типа, который реализует интерфейс IXmlSerializable и относится к определенному ранее типу содержимого, сериализатор записывает элемент-оболочку для элемента данных и передает управление методу WriteXml. Реализация WriteXml может записывать любой XML-код, включая добавление атрибутов в элемент-оболочку. После выполнения WriteXml сериализатор закрывает элемент.

При десериализации элемента данных типа, который реализует интерфейс IXmlSerializable и относится к определенному ранее типу содержимого, десериализатор помещает модуль чтения XML в элемент-оболочку для элемента данных и передает управление методу ReadXml. Метод должен прочесть весь элемент, включая открывающий и закрывающий теги. Убедитесь, что код ReadXml обрабатывает случай, когда элемент пуст. Кроме того, реализация ReadXml не должна использовать элемент программы-оболочки, именованный особым образом. Имя, выбранное сериализатором, может изменяться.

Разрешается полиморфно присваивать типы содержимого IXmlSerializable, например, элементам данных типа Object. Кроме того, для экземпляров типа разрешено значение null. И наконец, можно использовать типы IXmlSerializable с включенным режимом сохранения графов объектов и с NetDataContractSerializer. Все эти функции требуют, чтобы сериализатор WCF присоединял определенные атрибуты к элементу-оболочке (nil и type) в пространстве имен экземпляра схемы XML и "Id", "Ref", "Type" и "Assembly" в пространстве имен WCF.

Атрибуты, игнорируемые при реализации ReadXml

Перед передачей управления коду ReadXml десериализатор проверяет XML-элемент, обнаруживает данные специальные атрибуты XML и работает с ними. Например, если "nil" имеет значение true, десериализуется значение null, и ReadXml не вызывается. Если обнаружен полиморфизм, содержимое десериализуется так, как если бы это был другой тип. Вызывается реализация ReadXml полиморфно назначенного типа. В любом случае, реализация ReadXml игнорирует данные специальные атрибуты, поскольку они обрабатываются десериализатором.

Замечания по схемам для типов содержимого IXmlSerializable

При экспорте схемы и типа содержимого IXmlSerializable вызывается метод поставщика схемы. XmlSchemaSet передается в метод поставщика схемы. Метод может добавить любую допустимую схему в набор схем. Набор схем содержит схему, которая уже была известна на момент экспорта схемы. Если метод поставщика схем должен добавить элемент в набор схем, он должен определить, имеется ли в наборе схема XmlSchema с соответствующим пространством имен. Если это так, метод поставщика схемы должен добавить новый элемент в существующую схему XmlSchema. В противном случае, метод создает новый экземпляр XmlSchema. Это важно, если используются массивы типов IXmlSerializable. Например, если есть тип IXmlSerializable, который экспортируется как тип "A" в пространстве имен "Б", возможно, что к моменту вызова метода поставщика схем, набор схем уже содержит схему для "Б" для удержания типа "ArrayOfA".

Кроме добавления типов в XmlSchemaSet, метод поставщика схемы для типов содержимого должен возвратить ненулевое значение. Метод может возвратить XmlQualifiedName, указывающий имя типа схемы, которая будет использоваться для заданного типа IXmlSerializable. Полное имя также служит именем контракта данных и пространством имен для типа. Не разрешается возвращать тип, не существующий в наборе схем, немедленно при возврате метода поставщика. Однако предполагается, что к моменту экспорта всех типов (метод Export вызывается для всех соответствующих типов XsdDataContractExporter и выполняется доступ к свойству Schemas) тип существует в наборе схем. Доступ к свойству Schemas до того, как были выполнены все соответствующие вызовы Export, может привести к созданию исключения XmlSchemaException. Дополнительные сведения о процессе экспорта см. в разделе "Экспорт схем из классов".

Метод поставщика схемы также может возвратить тип XmlSchemaType для использования. Тип может быть или не быть анонимным. Если тип анонимный, схема для типа IXmlSerializable экспортируется как анонимный тип при каждом использовании типа IXmlSerializable в качестве элемента данных. Тип IXmlSerializable все еще имеет контракт данных и пространство имен. (Это определяется, как описано в описании Имена контрактов данных, за исключением того, что DataContractAttribute атрибут не может использоваться для настройки имени.) Если он не является анонимным, он должен быть одним из типов в объекте XmlSchemaSet. Данный случай эквивалентен возврату XmlQualifiedName типа.

Кроме того, для типа экспортируется глобальное объявление элемента. Если к типу не применен атрибут XmlRootAttribute, то элемент имеет те же имя и пространство имен, что и контракт данных, а его свойство nillable имеет значение true. Единственным исключением является пространство имен схемы (http://www.w3.org/2001/XMLSchema) — если контракт данных типа находится в этом пространстве имен, соответствующий глобальный элемент находится в пустом пространстве имен, поскольку запрещено добавлять новые элементы в пространство имен схемы. Если тип имеет применяемый к нему атрибут XmlRootAttribute, глобальное объявление элемента экспортируется с помощью свойств ElementName, Namespace и IsNullable. Значениями по умолчанию при применении атрибута XmlRootAttribute являются имя контракта данных, пустое пространство имен, а свойство «nillable» имеет значение true.

Те же правила объявления глобального элемента применяются и к устаревшим типам наборов данных. Важно отметить, что XmlRootAttribute не может переопределить объявления глобальных элементов, добавленных с помощью пользовательского кода или добавленных в набор схем XmlSchemaSet с помощью метода поставщика схем или посредством метода GetSchema для устаревших типов наборов данных.

Типы элемента IXmlSerializable

Типы элементов IXmlSerializable либо имеют свойство IsAny, которому присвоено значение true, либо их метод поставщика схем возвращает значение null.

Сериализация и десериализация типа элемента очень похожа на сериализацию и десериализацию типа содержимого. Однако есть некоторые важные отличия.

  • Как правило, реализация WriteXml записывает только один элемент (который, конечно, может содержать несколько дочерних элементов). Она не должна записывать атрибуты вне данного одиночного элемента, несколько родственных элементов или смешанное содержимое. Элемент может быть пустым.

  • Реализация ReadXml не должна прочитывать элемент программы-оболочки. Как правило, реализация прочитывает один элемент, создаваемый методом WriteXml.

  • При регулярной сериализации типа элемента (например, как элемента данных в контракте данных) сериализатор, как и в случае с типами содержимого, выводит элемент программы-оболочки до вызова метода WriteXml. Однако при сериализации типа элемента на верхнем уровне сериализатор обычно не выводит элемент-оболочку в окружение элемента, который записывается методом WriteXml, кроме случая, когда корневое имя и пространство имен явно заданы при конструировании сериализатора в конструкторах DataContractSerializer или NetDataContractSerializer. Дополнительные сведения см. в разделе Сериализация и десериализация.

  • При сериализации типа элемента на верхнем уровне без указания корневого имени и пространства имен во время создания WriteStartObject и WriteEndObject обычно не выполняют никаких операций, а WriteObjectContent вызывает WriteXml. В данном режиме сериализуемый объект не может иметь значение null и не может быть назначен полиморфно. Кроме того, не может быть включено сохранение графов объектов и не может использоваться NetDataContractSerializer.

  • При десериализации типа элемента на верхнем уровне без указания корневого имени и пространства имен во время построения IsStartObject возвращает значение true, если не может найти начало хотя бы одного из элементов. ReadObject с параметром verifyObjectName, установленным в значение true, работает таким же образом, как и IsStartObject перед фактическим считыванием объекта. Затем ReadObject передает управление методу ReadXml.

Схема, экспортированная для типов элементов, аналогична схеме для типа XmlElement, как описано в предыдущем разделе, за исключением того, что метод поставщика схемы может добавлять дополнительную схему в XmlSchemaSet как типы содержимого. Использование атрибута XmlRootAttribute с типами элемента не разрешено, и для данных типов никогда не выдаются глобальные объявления элемента.

Отличия от XmlSerializer

Сериализатор IXmlSerializable также понимает интерфейс XmlSchemaProviderAttribute и атрибуты XmlRootAttribute и XmlSerializer. Однако есть некоторые отличия в том, как данные атрибуты обрабатываются в модели контракта данных. Сводка важнейших отличий представлена ниже.

  • Метод поставщика схемы должен быть открытым для использования в XmlSerializer, но не должен быть открытым для использования в модели контракта данных.

  • Метод поставщика схемы вызывается, если свойству IsAny имеет значение true в модели контракта данных, но не с XmlSerializer.

  • Если атрибут XmlRootAttribute не присутствует в содержимом или устаревших типах набора данных, сериализатор XmlSerializer экспортирует глобальное объявление элемента в пустое пространство имени. В модели контракта данных используемым пространством имен обычно является пространство имен контракта данных, как было описано ранее.

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

Импорт схемы IXmlSerializable

При импорте схемы, созданной из типов IXmlSerializable, существует несколько возможностей.

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

  • Созданная схема может не быть действительной схемой контракта данных. Например, метод поставщика схемы может создать схему, которая включает XML-атрибуты, не поддерживаемые в модели контракта данных. В данном случае можно импортировать схему как типы IXmlSerializable. Этот режим импорта не включен по умолчанию, но его можно легко включить. Например, при /importXmlTypes переключении командной строки на средство служебной программы метаданных ServiceModel (Svcutil.exe). Это подробно описано в импорте схемы для создания классов. Обратите внимание, что работать нужно непосредственно с XML собственных экземпляров типа. Кроме того, следует принимать во внимание другую технологию сериализации, поддерживающую широкий диапазон схем - см. раздел, посвященный использованию XmlSerializer.

  • Возможно, вам понадобится повторно использовать существующие типы IXmlSerializable в прокси, вместо того чтобы создавать новые. В таком случае для указания типа для повторного использования может использоваться функция ссылочных типов, описанная в разделе «Импорт схемы для создания типов». Это соответствует использованию параметра /reference программы svcutil.exe, указывающего на сборку, которая содержит типы для повторного использования.

Поведение предыдущих версий XmlSerializer

В .NET Framework 4.0 и более ранних версиях XmlSerializer формировал временные сборки сериализации путем записи кода C# в файл. Затем файл компилировался в сборку. Это имело некоторые нежелательные последствия, например замедление времени запуска для сериализатора. В .NET Framework 4.5 сборки формируются без использования компилятора. Некоторым разработчикам может потребоваться просмотр сформированного кода C#. Вернуться к поведению предыдущих версий можно с помощью следующих настроек:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.xml.serialization>
    <xmlSerializer tempFilesLocation='e:\temp\XmlSerializerBug' useLegacySerializerGeneration="true" />
  </system.xml.serialization>
  <system.diagnostics>
    <switches>
      <add name="XmlSerialization.Compilation" value="1" />
    </switches>
  </system.diagnostics>
</configuration>

При возникновении проблем совместимости, таких как XmlSerializer сбой сериализации производного класса с недоступной новой переопределением, можно переключиться обратно в XMLSerializer устаревшее поведение с помощью следующей конфигурации:

<configuration>
  <appSettings>
    <add key="System:Xml:Serialization:UseLegacySerializerGeneration" value="true" />
  </appSettings>
</configuration>

В качестве альтернативы приведенной выше конфигурации можно использовать следующую конфигурацию на компьютере под управлением платформа .NET Framework версии 4.5 или более поздней версии:

<configuration>
  <system.xml.serialization>
    <xmlSerializer useLegacySerializerGeneration="true"/>
  </system.xml.serialization>
</configuration>

Примечание.

Переключатель <xmlSerializer useLegacySerializerGeneration="true"/> работает только на компьютере под управлением платформа .NET Framework версии 4.5 или более поздней версии. Приведенный выше appSettings подход работает на всех платформа .NET Framework версиях.

См. также