Использование класса 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 версиях.