Поделиться через


Использование класса 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, или используйте стиль оболочечного параметра.

Если вы получаете такое исключение, проверьте, применяется ли эта ситуация.

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" в пространстве имен "B", то возможно, что к моменту вызова метода поставщика схем уже существует набор схем, который содержит схему для "B" для хранения типа ArrayOfA.

В дополнение к добавлению типов в XmlSchemaSet, метод схемы поставщика для типов контента должен возвращать ненулевое значение. Он может возвращать XmlQualifiedName, указывающий имя типа схемы для использования данного типа IXmlSerializable. Это квалифицированное имя также служит именем контракта данных и пространством имен для данного типа. Разрешается возвращать тип, который не существует в наборе схем сразу после возврата метода поставщика схемы. Однако ожидается, что к тому времени, когда экспортируются все связанные типы (метод Export вызывается для всех соответствующих типов и осуществляется обращение к свойству XsdDataContractExporterSchemas), тип существует в наборе схем. Если получить доступ к свойству 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.

См. также