Compartilhar via


Usando a classe XmlSerializer

A WCF (Windows Communication Foundation) 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 classe DataContractSerializer para serializar tipos de dados. Esse serializador dá suporte aos 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 atributo DataContractAttribute).

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

  • Tipos que implementam a interface IXmlSerializable.

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

Muitos tipos de .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 consultar uma lista completa, confira Como especificar a transferência de dados em Contratos de serviço.

O DataContractSerializer, usado junto com tipos de contrato de dados, é a maneira recomendada de escrever novos serviços WCF. Para saber mais, confira Como usar contratos de dados.

XmlSerializer

O WCF também dá suporte à classe XmlSerializer. A classe XmlSerializer não é exclusiva do WCF. Ela é o mesmo mecanismo de serialização que os serviços Web do ASP.NET usam. A classe XmlSerializer dá suporte a um conjunto muito mais estreito de tipos do que a classe DataContractSerializer, mas permite muito mais controle sobre o XML resultante e dá suporte a muito mais do padrão (XSD) da linguagem de definição XML Schema. Ela 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 classe XmlSerializer não dá suporte a tipos de contrato de dados.

Ao usar Svcutil.exe ou o recurso Adicionar Referência de Serviço no Visual Studio para gerar código do 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, é necessário alternar manualmente para o XmlSerializer. Isso acontece, por exemplo, nos seguintes casos:

  • Ao migrar um aplicativo de serviços Web do ASP.NET para o WCF, convém reutilizar tipos existentes e compatíveis com XmlSerializer 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 (Linguagem de Descrição dos Serviços Web) 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 seja 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 classe XmlSerializer aplicando o atributo XmlSerializerFormatAttribute 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

Observação

É 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ê usar acidentalmente o serializador errado, poderá estar divulgando informações do tipo que não pretende divulgar.

Por exemplo, a classe DataContractSerializer só serializa membros marcados com o atributo DataMemberAttribute ao serializar tipos de contrato de dados. A classe XmlSerializer serializa qualquer membro público. Confira 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 em que a classe XmlSerializer é selecionada, o membro creditCardNumber será serializado, o que provavelmente não é o objetivo.

Embora a classe DataContractSerializer seja o padrão, você pode selecioná-la explicitamente para o serviço (embora isso nunca deva ser necessário) aplicando o atributo DataContractFormatAttribute 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 associação diferente ou alterando outras configurações.

Outras considerações importantes de segurança se aplicam à classe XmlSerializer. Primeiro, é altamente recomendável que qualquer aplicativo WCF que use a classe XmlSerializer seja assinado com uma chave protegida contra a divulgação. Essa recomendação se aplica tanto quando uma opção manual para o XmlSerializer é executada quanto quando um comutador automático é executado (por Svcutil.exe, Adicionar Referência de Serviço ou uma ferramenta semelhante). Isso ocorre porque o mecanismo de serialização XmlSerializer dá suporte ao carregamento de assemblies de serialização pré-gerados, desde que sejam assinados com a mesma chave que o aplicativo. Um aplicativo sem sinal é completamente desprotegido da possibilidade de um assembly mal-intencionado corresponder ao nome esperado do assembly de serialização pré-gerado que está sendo 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 mecanismo de serialização XmlSerializer cria e usa assemblies de serialização temporários nessa pasta. Você deve estar ciente de que qualquer processo com acesso de gravação à pasta temporária pode substituir esses assemblies de serialização com código mal-intencionado.

Regras para suporte ao XmlSerializer

Não é possível aplicar tributos compatíveis com XmlSerializer diretamente a parâmetros de operação de contrato ou valores retornados. No entanto, eles podem ser aplicados a mensagens tipadas (partes do corpo do contrato de 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 mensagem tipadas, esses atributos substituem propriedades que entram em conflito nos atributos de mensagem tipadas. 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

Não há suporte para o atributo MessageHeaderArrayAttribute ao usar o XmlSerializer.

Observação

Nesse caso, o XmlSerializer gera a seguinte exceção, que é lançada 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 XmlElementAttribute, ou usando o estilo de parâmetro Wrapped".

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

O WCF não dá suporte aos atributos SoapIncludeAttribute e XmlIncludeAttribute em contratos de mensagem e contratos de operação; use o atributo KnownTypeAttribute em vez disso.

Tipos que implementam a interface IXmlSerialable

Tipos que implementam a interface IXmlSerializable têm compatibilidade total pelo DataContractSerializer. O atributo XmlSchemaProviderAttribute deve ser sempre aplicado a esses tipos para controlar seu esquema.

Aviso

Se você estiver serializando tipos polimórficos, deverá aplicar o tipo XmlSchemaProviderAttribute 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 DataSet herdados.

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

  • Os tipos de elemento são usados quando um tipo IXmlSerializable precisa controlar seu próprio nome de elemento raiz. Para marcar um tipo como um tipo de elemento, defina a propriedade IsAny no atributo XmlSchemaProviderAttribute 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 for true e um método de provedor de esquema for especificado, o método deverá retornar null.

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

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 método WriteXml. A implementação de WriteXml pode gravar qualquer XML, o que inclui a adição de atributos ao elemento wrapper. Depois que WriteXml é 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 método ReadXml. O método precisa ler todo o elemento, incluindo as marcas inicial e final. Verifique se o código ReadXml manipula o caso em que o elemento está vazio. Além disso, sua implementação de ReadXml não deve depender do elemento wrapper que está sendo nomeado de uma maneira específica. O nome escolhido pelo serializador pode variar.

É permitido atribuir tipos de conteúdo IXmlSerializable polimorficamente, por exemplo, a membros de dados do tipo Object. Também é permitido que as instâncias de tipo sejam nulas. Por fim, é possível usar tipos IXmlSerializable com a preservação do grafo de objeto habilitada e com o NetDataContractSerializer. Todos esses recursos exigem que o serializador WCF anexe determinados atributos ao elemento wrapper ("nil" e "type" no namespace da Instância de Esquema XML e "Id", "Ref", "Type" e "Assembly" em um namespace específico do WCF).

Atributos a serem ignorados ao implementar ReadXml

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

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

Ao exportar o esquema de um tipo de conteúdo IXmlSerializable, 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 de esquema. Quando o método do provedor de esquema precisa adicionar um item ao conjunto de esquemas, ele precisa determinar se já existe um XmlSchema com o namespace apropriado no conjunto. Se isso acontecer, o método do provedor de esquema precisa adicionar o novo item ao XmlSchema existente. Caso contrário, ele precisa criar uma nova instância XmlSchema. Isso é importante se matrizes de tipos IXmlSerializable estiverem sendo usadas. Por exemplo, se você tiver um tipo IXmlSerializable exportado como tipo "A" no namespace "B", é possível que, quando o método do provedor de esquema for chamado, o esquema já contenha o esquema de "B" para manter o tipo "ArrayOfA".

Além de adicionar tipos ao XmlSchemaSet, o método de provedor de esquema para tipos de conteúdo precisa retornar um valor não nulo. Ele pode retornar um XmlQualifiedName que especifica o nome do tipo de esquema a ser usado para o tipo IXmlSerializable fornecido. Esse nome qualificado também serve como o nome e o namespace do contrato de dados para o tipo. É permitido retornar um tipo que não existe no esquema definido imediatamente quando o método do provedor de esquema retorna. No entanto, espera-se que, quando todos os tipos relacionados forem exportados (o método Export é chamado para todos os tipos relevantes no XsdDataContractExporter e a Schemas propriedade é acessada), o tipo exista no conjunto de esquemas. Acessar a propriedade Schemas antes que todas as chamadas Export relevantes tenham sido feitas pode resultar em um XmlSchemaException. 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 IXmlSerializable será exportado como um tipo anônimo sempre que o tipo IXmlSerializable for usado como membro de dados. O tipo IXmlSerializable ainda tem um namespace e um nome de contrato de dados. (Isso é determinado conforme descrito em Nomes de Contrato de Dados , exceto que o atributo DataContractAttribute não pode ser usado para personalizar o nome.) Se não for anônimo, ele precisará ser um dos tipos no XmlSchemaSet. Esse caso é equivalente a retornar o XmlQualifiedName do tipo.

Além disso, uma declaração de elemento global é exportada para o tipo. Se o tipo não tiver o atributo XmlRootAttribute 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 de 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 de esquema. Se o tipo tiver o atributo XmlRootAttribute aplicado a ele, a declaração de elemento global será exportada usando o seguinte: propriedades ElementName, Namespace e IsNullable. Os padrões com XmlRootAttribute aplicados 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 aos tipos de conjunto de dados herdados. Observe que não é possível substituir declarações XmlRootAttribute de elemento global adicionadas por meio de código personalizado, adicionadas ao XmlSchemaSet usando o método de provedor de esquema ou por meio de GetSchema para tipos de conjunto de dados herdados.

Tipos de elemento IXmlSerializable

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

Serializar e desserializar um tipo de elemento é muito semelhante à serialização e desserialização de um tipo de conteúdo. No entanto há algumas diferenças importantes:

  • Espera-se que a implementação de WriteXml grave exatamente um elemento (que, naturalmente, poderia conter vários elementos filho). Ele não deve escrever atributos fora desse único elemento, vários elementos irmãos ou conteúdo misto. O elemento pode estar vazio.

  • A implementação de ReadXml não deve ler o elemento wrapper. Espera-se que ele 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 gera 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 gera um elemento wrapper em torno do elemento que WriteXml grava, a menos que um nome raiz e namespace tenham sido explicitamente especificados ao construir o serializador nos construtores DataContractSerializer ou NetDataContractSerializer. Para obter mais informações, consulte Serialização e desserialização.

  • Ao serializar um tipo de elemento no nível superior sem especificar o nome raiz e o namespace em tempo de construção, WriteStartObject e WriteEndObject essencialmente não fazem nada e WriteObjectContent chama 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 grafo de objeto não pode ser habilitada e o NetDataContractSerializer não pode ser usado.

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

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

Diferenças do XmlSerializer

A interface IXmlSerializable e os atributos XmlSchemaProviderAttribute e XmlRootAttribute são também reconhecidos pelo XmlSerializer. No entanto, há algumas diferenças na forma como eles são tratadas no modelo de contrato de dados. As diferenças importantes estão resumidas a seguir:

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

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

  • Quando o atributo XmlRootAttribute não está presente para tipos de conteúdo ou conjunto de dados herdados, o XmlSerializer exporta uma declaração do 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.

Lembre-se dessas diferenças ao criar tipos usados com ambas as tecnologias de serialização.

Importando esquema IXmlSerializable

Ao importar um esquema gerado de tipos IXmlSerializable, há algumas possibilidades:

  • O esquema gerado pode ser um esquema de contrato de dados válido, conforme descrito na 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 um esquema que envolve atributos XML que não são compatíveis no modelo de contrato de dados. Nesse caso, você pode importar o esquema como tipos IXmlSerializable. Esse modo de importação não está ativado por padrão, mas pode ser facilmente habilitado – por exemplo, com a opção de linha de comando /importXmlTypes para a Ferramenta de Utilitário de Metadados do ServiceModel (Svcutil.exe). Isso é descrito em detalhes em Importando esquemas para gerar classes. Observe que você precisa 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 permite uma gama mais ampla de esquemas. Veja o tópico sobre como usar o XmlSerializer.

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

Comportamento herdado de XmlSerializer

No .NET Framework 4.0 e anterior, o XmlSerializer gerou assemblies de serialização temporários escrevendo código C# em um arquivo. Em seguida, o arquivo foi compilado em um assembly. Esse comportamento teve algumas consequências indesejáveis, como diminuir o tempo de inicialização do 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ê encontrar problemas de compatibilidade, como a falha de XmlSerializer em serializar uma classe derivada com uma nova substituição não pública, poderá voltar para o comportamento herdado XMLSerializer 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 um computador que executa .NET Framework versão 4.5 ou posterior:

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

Observação

O comutador <xmlSerializer useLegacySerializerGeneration="true"/> só funciona em um computador que executa .NET Framework versão 4.5 ou posterior. A abordagem de appSettings acima funciona em todas as versões .NET Framework.

Confira também