Partilhar via


Usando a classe XmlSerializer

O Windows Communication Foundation (WCF) pode usar duas tecnologias de serialização diferentes para transformar os dados em seu aplicativo em XML que é transmitido entre clientes e serviços: DataContractSerializer e XmlSerializer.

DataContractSerializer

Por padrão, o WCF usa a DataContractSerializer classe para serializar tipos de dados. Este serializador suporta os seguintes tipos:

  • Tipos primitivos (por exemplo, inteiros, cadeias de caracteres e matrizes de bytes), bem como alguns tipos especiais, como XmlElement e DateTime, que são tratados como primitivos.

  • Tipos de contrato de dados (tipos marcados com o DataContractAttribute atributo).

  • Tipos marcados com o SerializableAttribute atributo, que incluem tipos que implementam a ISerializable interface.

  • Tipos que implementam a IXmlSerializable interface.

  • Muitos tipos de coleção comuns, que incluem muitos tipos de coleção genéricos.

Muitos tipos do .NET Framework se enquadram nas duas últimas categorias e, portanto, são serializáveis. Matrizes de tipos serializáveis também são serializáveis. Para obter uma lista completa, consulte Especificando a transferência de dados em contratos de serviço.

O DataContractSerializer, usado em conjunto com tipos de contrato de dados, é a maneira recomendada de escrever novos serviços WCF. Para obter mais informações, consulte Usando contratos de dados.

XmlSerializer

WCF também suporta a XmlSerializer classe. A XmlSerializer classe não é exclusiva do WCF. É o mesmo mecanismo de serialização que ASP.NET serviços da Web usam. A XmlSerializer classe suporta um conjunto muito mais restrito de tipos do que a DataContractSerializer classe, mas permite muito mais controle sobre o XML resultante e suporta muito mais do padrão XSD (XML Schema Definition Language). Ele também não requer nenhum atributo declarativo nos tipos serializáveis. Para obter mais informações, consulte o tópico Serialização XML na documentação do .NET Framework. A XmlSerializer classe não suporta tipos de contrato de dados.

Ao usar o Svcutil.exe ou o recurso Adicionar referência de serviço no Visual Studio para gerar código de cliente para um serviço de terceiros ou para acessar um esquema de terceiros, um serializador apropriado é selecionado automaticamente para você. Se o esquema não for compatível com o DataContractSerializer, o XmlSerializer será selecionado.

Alternar para o XmlSerializer

Às vezes, você pode ter que alternar manualmente para o XmlSerializer. Isso acontece, por exemplo, nos seguintes casos:

  • Ao migrar um aplicativo de ASP.NET Web Services para WCF, convém reutilizar tipos existentes XmlSerializercompatíveis em vez de criar novos tipos de contrato de dados.

  • Quando o controle preciso sobre o XML que aparece nas mensagens é importante, mas um documento WSDL (Web Services Description Language) não está disponível, por exemplo, ao criar um serviço com tipos que precisam estar em conformidade com um determinado esquema padronizado e publicado que não é compatível com o DataContractSerializer.

  • Ao criar serviços que seguem o padrão de codificação SOAP herdado.

Nesses e em outros casos, você pode alternar manualmente para a XmlSerializer classe aplicando o XmlSerializerFormatAttribute atributo ao seu serviço, conforme mostrado no código a seguir.

[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.

Considerações de Segurança

Nota

É importante ter cuidado ao alternar mecanismos de serialização. O mesmo tipo pode serializar para XML de forma diferente, dependendo do serializador que está sendo usado. Se você acidentalmente usar o serializador errado, você pode estar divulgando informações do tipo que você não pretendia divulgar.

Por exemplo, a DataContractSerializer classe serializa apenas membros marcados com o DataMemberAttribute atributo ao serializar tipos de contrato de dados. A XmlSerializer classe serializa qualquer membro público. Veja o tipo no código a seguir.

[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

Se o tipo for usado inadvertidamente em um contrato de serviço onde a XmlSerializer classe é selecionada, o membro é serializado, o creditCardNumber que provavelmente não é pretendido.

Embora a DataContractSerializer classe seja o padrão, você pode selecioná-la explicitamente para seu serviço (embora isso nunca deva ser necessário) aplicando o DataContractFormatAttribute atributo ao tipo de contrato de serviço.

O serializador usado para o serviço é parte integrante do contrato e não pode ser alterado selecionando uma ligação diferente ou alterando outras definições de configuração.

Outras considerações de segurança importantes se aplicam à XmlSerializer classe. Primeiro, é altamente recomendável que qualquer aplicativo WCF que use a XmlSerializer classe seja assinado com uma chave protegida contra divulgação. Esta recomendação aplica-se tanto quando uma mudança manual para o é executada como quando uma mudança automática é executada XmlSerializer (por Svcutil.exe, Adicionar Referência de Serviço ou uma ferramenta semelhante). Isso ocorre porque o XmlSerializer mecanismo de serialização suporta o carregamento de assemblies de serialização pré-gerados, desde que eles sejam assinados com a mesma chave que o aplicativo. Um aplicativo não assinado é completamente desprotegido contra a possibilidade de um assembly mal-intencionado correspondente ao nome esperado do assembly de serialização pré-gerado ser colocado na pasta do aplicativo ou no cache de assembly global. É claro que um invasor deve primeiro obter acesso de gravação a um desses dois locais para tentar essa ação.

Outra ameaça que existe sempre que você usa XmlSerializer está relacionada ao acesso de gravação à pasta temporária do sistema. O XmlSerializer mecanismo de serialização cria e usa assemblies de serialização temporários nesta pasta. Você deve estar ciente de que qualquer processo com acesso de gravação à pasta temporária pode substituir esses assemblies de serialização por código mal-intencionado.

Regras para suporte a XmlSerializer

Não é possível aplicar XmlSerializerdiretamente atributos compatíveis a parâmetros de operação de contrato ou valores de retorno. No entanto, eles podem ser aplicados a mensagens digitadas (partes do corpo do contrato da mensagem), conforme mostrado no código a seguir.

[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

Quando aplicados a membros de mensagens digitadas, esses atributos substituem propriedades que entram em conflito nos atributos de mensagem digitada. Por exemplo, no código a seguir, ElementName substitui 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

O MessageHeaderArrayAttribute atributo não é suportado ao usar o XmlSerializerarquivo .

Nota

Nesse caso, o XmlSerializer lança a seguinte exceção, que é liberada antes do WCF: "Um elemento declarado no nível superior de um esquema não pode ter maxOccurs> 1. Forneça um elemento wrapper para 'mais' usando XmlArray ou XmlArrayItem em vez de , ou usando o estilo de XmlElementAttributeparâmetro Wrapped."

Se você receber tal exceção, investigue se essa situação se aplica.

O WCF não suporta os SoapIncludeAttribute atributos e XmlIncludeAttribute em contratos de mensagem e contratos de operação, use o KnownTypeAttribute atributo em vez disso.

Tipos que implementam a interface IXmlSerializable

Os tipos que implementam a IXmlSerializable interface são totalmente suportados DataContractSerializerpelo . O XmlSchemaProviderAttribute atributo deve sempre ser aplicado a esses tipos para controlar seu esquema.

Aviso

Se você estiver serializando tipos polimórficos, deverá aplicar o XmlSchemaProviderAttribute ao tipo para garantir que o tipo correto seja serializado.

Há três variedades de tipos que implementam IXmlSerializable: tipos que representam conteúdo arbitrário, tipos que representam um único elemento e tipos herdados DataSet .

  • Os tipos de conteúdo usam um método de provedor de esquema especificado pelo XmlSchemaProviderAttribute atributo. O método não retorna null e a IsAny propriedade no atributo é deixada em seu valor padrão de false. Este é o uso mais comum de IXmlSerializable tipos.

  • Os tipos de elemento são usados quando um tipo deve controlar seu próprio nome de IXmlSerializable elemento raiz. Para marcar um tipo como um tipo de elemento, defina a IsAnyXmlSchemaProviderAttribute propriedade no atributo como true ou retorne null do método do provedor de esquema. Ter um método de provedor de esquema é opcional para tipos de elemento – você pode especificar null em vez do nome do método no XmlSchemaProviderAttribute. No entanto, se IsAny é true e um método de provedor de esquema é especificado, o método deve retornar null.

  • Tipos herdados DataSet são IXmlSerializable tipos que não são marcados com o XmlSchemaProviderAttribute atributo. Em vez disso, eles dependem do método para a geração do GetSchema esquema. Esse padrão é usado para o DataSet tipo e seu conjunto de dados tipado deriva uma classe em versões anteriores do .NET Framework, mas agora está obsoleto e é suportado apenas por motivos herdados. Não confie neste padrão e aplique sempre o XmlSchemaProviderAttribute aos seus IXmlSerializable tipos.

Tipos de conteúdo IXmlSerializable

Ao serializar um membro de dados de um tipo que implementa IXmlSerializable e é um tipo de conteúdo conforme definido anteriormente, o serializador grava o elemento wrapper para o membro de dados e passa o controle para o WriteXml método. A WriteXml implementação pode escrever qualquer XML, o que inclui a adição de atributos ao elemento wrapper. Depois WriteXml de concluído, o serializador fecha o elemento.

Ao desserializar um membro de dados de um tipo que implementa IXmlSerializable e é um tipo de conteúdo conforme definido anteriormente, o desserializador posiciona o leitor XML no elemento wrapper para o membro de dados e passa o controle para o ReadXml método. O método deve ler o elemento inteiro, incluindo as tags start e end. Certifique-se de que seu ReadXml código lida com o caso em que o elemento está vazio. Além disso, sua ReadXml implementação não deve depender do elemento wrapper ser nomeado de uma maneira específica. O nome escolhido pelo serializador pode variar.

É permitido atribuir IXmlSerializable tipos de conteúdo polimorficamente, por exemplo, a membros de dados do tipo Object. Também é permitido que as instâncias de tipo sejam nulas. Finalmente, é possível usar IXmlSerializable tipos com a preservação de gráficos de objetos ativada e com o NetDataContractSerializer. Todos esses recursos exigem que o serializador WCF anexe certos atributos ao elemento wrapper ("nil" e "type" no namespace XML Schema Instance e "Id", "Ref", "Type" e "Assembly" em um namespace específico do WCF).

Atributos a serem ignorados ao implementar o ReadXml

Antes de passar o controle para o ReadXml código, o desserializador examina o elemento XML, deteta esses atributos XML especiais e age sobre eles. Por exemplo, se "nil" for true, um valor nulo será desserializado e ReadXml não será chamado. Se o polimorfismo for detetado, o conteúdo do elemento será desserializado como se fosse um tipo diferente. A implementação do ReadXml tipo atribuído polimorficamente é chamada. Em qualquer caso, uma ReadXml implementação deve ignorar esses atributos especiais porque eles são manipulados pelo desserializador.

Considerações de esquema para tipos de conteúdo IXmlSerializable

Ao exportar o esquema e um tipo de IXmlSerializable conteúdo, o método do provedor de esquema é chamado. Um XmlSchemaSet é passado para o método do provedor de esquema. O método pode adicionar qualquer esquema válido ao conjunto de esquemas. O conjunto de esquemas contém o esquema que já é conhecido no momento em que ocorre a exportação do esquema. Quando o método do provedor de esquema deve adicionar um item ao conjunto de esquemas, ele deve determinar se um XmlSchema com o namespace apropriado já existe no conjunto. Se isso acontecer, o método do provedor de esquema deve adicionar o novo item ao arquivo XmlSchema. Caso contrário, ele deve criar uma nova XmlSchema instância. Isso é importante se matrizes de IXmlSerializable tipos estiverem sendo usadas. Por exemplo, se você tiver um IXmlSerializable tipo que é exportado como tipo "A" no namespace "B", é possível que, no momento em que o método do provedor de esquema é chamado, o conjunto de esquemas já contenha o esquema para "B" para conter o tipo "ArrayOfA".

Além de adicionar tipos ao XmlSchemaSet, o método do provedor de esquema para tipos de conteúdo deve retornar um valor não nulo. Ele pode retornar um XmlQualifiedName que especifica o nome do tipo de esquema a ser usado para determinado IXmlSerializable tipo. Esse nome qualificado também serve como o nome do contrato de dados e namespace para o tipo. É permitido retornar um tipo que não existe no conjunto de esquemas imediatamente quando o método do provedor de esquema retorna. No entanto, espera-se que, no momento em que todos os tipos relacionados são exportados (o Export método é chamado para todos os tipos relevantes no e a XsdDataContractExporterSchemas propriedade é acessada), o tipo existe no conjunto de esquemas. O acesso à propriedade antes de Schemas todas as chamadas relevantes Export terem sido feitas pode resultar em um XmlSchemaExceptionarquivo . Para obter mais informações sobre o processo de exportação, consulte Exportando esquemas de classes.

O método do provedor de esquema também pode retornar o XmlSchemaType para usar. O tipo pode ou não ser anónimo. Se for anônimo, o esquema do tipo será exportado IXmlSerializable como um tipo anônimo toda vez que o IXmlSerializable tipo for usado como membro de dados. O IXmlSerializable tipo ainda tem um nome de contrato de dados e namespace. (Isto é determinado conforme descrito em Nomes de Contrato de Dados, exceto que o DataContractAttribute atributo não pode ser usado para personalizar o nome.) Se não for anónimo, deve ser um dos tipos no XmlSchemaSet. Este caso equivale a devolver o XmlQualifiedName do tipo.

Além disso, uma declaração de elemento global é exportada para o tipo. Se o tipo não tiver o XmlRootAttribute atributo aplicado a ele, o elemento terá o mesmo nome e namespace que o contrato de dados, e sua propriedade "nillable" será true. A única exceção a isso é o namespace do esquema (http://www.w3.org/2001/XMLSchema) – se o contrato de dados do tipo estiver nesse namespace, o elemento global correspondente estará no namespace em branco porque é proibido adicionar novos elementos ao namespace do esquema. Se o tipo tiver o XmlRootAttribute atributo aplicado a ele, a declaração de elemento global será exportada usando o seguinte: ElementName, Namespace e IsNullable propriedades. Os padrões com XmlRootAttribute aplicado são o nome do contrato de dados, um namespace em branco e "nillable" sendo true.

As mesmas regras de declaração de elemento global se aplicam a tipos de conjuntos de dados herdados. Observe que o XmlRootAttribute não pode substituir declarações de elemento global adicionadas por meio de código personalizado, adicionado ao XmlSchemaSet usando o método de provedor de esquema ou através de tipos de GetSchema conjunto de dados herdados.

Tipos de elementos IXmlSerializable

IXmlSerializable Os tipos de elemento têm a IsAny propriedade definida como true ou têm seu método de provedor de esquema retornado null.

Serializar e desserializar um tipo de elemento é muito semelhante a serializar e desserializar um tipo de conteúdo. No entanto, existem algumas diferenças importantes:

  • Espera-se que a WriteXml implementação escreva exatamente um elemento (que pode, é claro, conter vários elementos filho). Não deve escrever atributos fora deste único elemento, vários elementos irmãos ou conteúdo misto. O elemento pode estar vazio.

  • A ReadXml implementação não deve ler o elemento wrapper. Espera-se que leia o único elemento que WriteXml produz.

  • Ao serializar um tipo de elemento regularmente (por exemplo, como um membro de dados em um contrato de dados), o serializador produz um elemento wrapper antes de chamar WriteXml, como acontece com os tipos de conteúdo. No entanto, ao serializar um tipo de elemento no nível superior, o serializador normalmente não produz um elemento wrapper em torno do elemento que escreve, a menos que WriteXml um nome de raiz e namespace sejam explicitamente especificados ao construir o serializador nos DataContractSerializer construtores or NetDataContractSerializer . Para obter mais informações, consulte Serialização e desserialização.

  • Ao serializar um elemento, digite no nível superior sem especificar o nome da raiz e o namespace no momento da construção eWriteEndObject, essencialmente, WriteStartObject não faça nada e WriteObjectContent chame WriteXml. Nesse modo, o objeto que está sendo serializado não pode ser null e não pode ser atribuído polimorficamente. Além disso, a preservação do gráfico de objeto não pode ser ativada e o NetDataContractSerializer não pode ser usado.

  • Ao desserializar um tipo de elemento no nível superior sem especificar o nome da raiz e o namespace no momento da construção, IsStartObject retorna true se puder encontrar o início de qualquer elemento. ReadObject com o verifyObjectName parâmetro definido para true se comportar da mesma maneira que IsStartObject antes de realmente ler o objeto. ReadObject em seguida, passa o controle para o ReadXml método.

O esquema exportado para tipos de elemento é o mesmo que para o XmlElement tipo descrito em uma seção anterior, exceto que o método de provedor de esquema pode adicionar qualquer esquema adicional ao XmlSchemaSet como com tipos de conteúdo. O uso do atributo com tipos de XmlRootAttribute elemento não é permitido, e declarações de elemento global nunca são emitidas para esses tipos.

Diferenças do XmlSerializer

A IXmlSerializable interface e os XmlSchemaProviderAttribute atributos e XmlRootAttribute também são compreendidos XmlSerializer pelo . No entanto, existem algumas diferenças na forma como estes são tratados no modelo de contrato de dados. As diferenças importantes estão resumidas na seguinte lista:

  • O método do provedor de esquema deve ser público para ser usado no XmlSerializer, mas não precisa ser público para ser usado no modelo de contrato de dados.

  • O método do provedor de esquema é chamado quando IsAny está true no modelo de contrato de dados, mas não com o XmlSerializer.

  • Quando o XmlRootAttribute atributo não está presente para tipos de conteúdo ou conjunto de dados herdados, o exporta XmlSerializer uma declaração de elemento global no namespace em branco. No modelo de contrato de dados, o namespace usado normalmente é o namespace do contrato de dados, conforme descrito anteriormente.

Esteja ciente dessas diferenças ao criar tipos que são usados com ambas as tecnologias de serialização.

Importando esquema IXmlSerializable

Ao importar um esquema gerado a partir de IXmlSerializable tipos, existem algumas possibilidades:

  • O esquema gerado pode ser um esquema de contrato de dados válido, conforme descrito em Referência de esquema de contrato de dados. Nesse caso, o esquema pode ser importado como de costume e tipos de contrato de dados regulares são gerados.

  • O esquema gerado pode não ser um esquema de contrato de dados válido. Por exemplo, seu método de provedor de esquema pode gerar esquema que envolve atributos XML que não são suportados no modelo de contrato de dados. Nesse caso, você pode importar o esquema como IXmlSerializable tipos. Esse modo de importação não está ativado por padrão, mas pode ser facilmente habilitado – por exemplo, com a opção de /importXmlTypes linha de comando para a ServiceModel Metadata Utility Tool (Svcutil.exe). Isso é descrito em detalhes no esquema de importação para gerar classes. Observe que você deve trabalhar diretamente com o XML para suas instâncias de tipo. Você também pode considerar o uso de uma tecnologia de serialização diferente que ofereça suporte a uma gama mais ampla de esquema – consulte o tópico sobre como usar o XmlSerializer.

  • Você pode querer reutilizar seus tipos existentes IXmlSerializable no proxy em vez de gerar novos. Nesse caso, o recurso de tipos referenciados descrito no tópico Importando esquema para gerar tipos pode ser usado para indicar o tipo a ser reutilizado. Isso corresponde ao uso do /reference svcutil.exe de ativação, que especifica o assembly que contém os tipos a serem reutilizados.

Comportamento herdado do XmlSerializer

No .NET Framework 4.0 e anteriores, o XmlSerializer gerou assemblies de serialização temporários gravando código C# em um arquivo. O arquivo foi então compilado em um assembly. Esse comportamento teve algumas consequências indesejáveis, como diminuir o tempo de inicialização para o serializador. No .NET Framework 4.5, esse comportamento foi alterado para gerar os assemblies sem exigir o uso do compilador. Alguns desenvolvedores podem querer ver o código C# gerado. Você pode especificar para usar esse comportamento herdado pela seguinte configuração:

<?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>

Se você tiver problemas de compatibilidade, como a falha ao serializar uma classe derivada XmlSerializer com uma nova substituição não pública, poderá voltar para o XMLSerializer comportamento herdado usando a seguinte configuração:

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

Como alternativa à configuração acima, você pode usar a seguinte configuração em uma máquina executando o .NET Framework 4.5 ou versão posterior:

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

Nota

O <xmlSerializer useLegacySerializerGeneration="true"/> switch só funciona em uma máquina que executa o .NET Framework 4.5 ou versão posterior. A abordagem acima appSettings funciona em todas as versões do .NET Framework.

Consulte também