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


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

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

DataContractSerializer по умолчанию

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

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

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

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

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

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

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

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

Когда использовать класс 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 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.
[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.

Вопросы безопасности

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ms733901.Warning(ru-ru,VS.100).gif Внимание!
Если выполняется сериализация полиморфных типов, необходимо применить атрибут XmlSchemaProviderAttribute к типу, чтобы убедиться, что сериализуется правильный тип.

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

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

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

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

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

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

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

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

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

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

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

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

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

См. также

Задачи

Как сократить время запуска клиентских приложений WCF с использованием XmlSerializer

Справочник

DataContractFormatAttribute
DataContractSerializer
XmlSerializer
MessageHeaderArrayAttribute

Основные понятия

Задание передачи данных в контрактах служб
Использование контрактов данных