Controle de versão de contrato de dados
À medida que os aplicativos evoluem, talvez você também precise alterar os contratos de dados usados pelos serviços. Este tópico explica como versionar contratos de dados. Este tópico descreve os mecanismos de versão do contrato de dados. Para obter uma visão geral completa e orientação prescritiva sobre controle de versão, consulte Práticas recomendadas: controle de versão de contrato de dados.
Alterações interruptiva e não interruptiva
As alterações em um contrato de dados podem ser interruptiva e não interruptiva. Quando um contrato de dados é alterado de forma não interruptiva, um aplicativo que usa a versão mais antiga do contrato pode se comunicar com um aplicativo que usa a versão mais recente, e um aplicativo que usa a versão mais recente do contrato pode se comunicar com um aplicativo que usa a versão mais antiga. Por outro lado, uma mudança interruptiva impede a comunicação em uma ou ambas as direções.
Quaisquer alterações em um tipo que não afetem como ele é transmitido e recebido são não interruptivas. Tais alterações não alteram o contrato de dados, apenas o tipo subjacente. Por exemplo, você pode alterar o nome de um campo de maneira não interruptiva se definir a propriedade Name do DataMemberAttribute para o nome da versão mais antiga. O código a seguir mostra a versão 1 de um contrato de dados.
// Version 1
[DataContract]
public class Person
{
[DataMember]
private string Phone;
}
' Version 1
<DataContract()> _
Public Class Person
<DataMember()> _
Private Phone As String
End Class
O código a seguir mostra uma alteração não interruptiva.
// Version 2. This is a non-breaking change because the data contract
// has not changed, even though the type has.
[DataContract]
public class Person
{
[DataMember(Name = "Phone")]
private string Telephone;
}
' Version 2. This is a non-breaking change because the data contract
' has not changed, even though the type has.
<DataContract()> _
Public Class Person
<DataMember(Name:="Phone")> _
Private Telephone As String
End Class
Algumas alterações modificam os dados transmitidos, mas podem ou não ser interruptivas. As seguintes mudanças são sempre interruptivas:
Alterar a ordem dos membros de dados usando a propriedade Order do DataMemberAttribute.
Renomear um membro de dados.
Alterar o contrato de dados de um membro de dados. Por exemplo, alterar o tipo de membro de dados de um inteiro para uma cadeia de caracteres ou de um tipo com um contrato de dados chamado "Cliente" para um tipo com um contrato de dados chamado "Pessoa".
As seguintes alterações também são possíveis.
Adicionar e remover membros de dados
Na maioria dos casos, adicionar ou remover um membro de dados não é uma alteração importante, a menos que você exija validade de esquema estrita (novas instâncias validadas em relação ao esquema antigo).
Quando um tipo com um campo extra é desserializado em um tipo com um campo ausente, as informações extras são ignoradas. (Ele também pode ser armazenado para fins de ida e volta; para obter mais informações, consulte Contratos de dados compatíveis com o futuro).
Quando um tipo com um campo ausente é desserializado em um tipo com um campo extra, o campo extra é deixado em seu valor padrão, geralmente zero ou null
. (O valor padrão pode ser alterado; para obter mais informações, consulte Retornos de chamada de serialização tolerantes à versão).
Por exemplo, você pode usar a classe CarV1
em um cliente e a classe CarV2
em um serviço, ou pode usar a classe CarV1
em um serviço e a classe CarV2
em um cliente.
// Version 1 of a data contract, on machine V1.
[DataContract(Name = "Car")]
public class CarV1
{
[DataMember]
private string Model;
}
// Version 2 of the same data contract, on machine V2.
[DataContract(Name = "Car")]
public class CarV2
{
[DataMember]
private string Model;
[DataMember]
private int HorsePower;
}
' Version 1 of a data contract, on machine V1.
<DataContract(Name:="Car")> _
Public Class CarV1
<DataMember()> _
Private Model As String
End Class
' Version 2 of the same data contract, on machine V2.
<DataContract(Name:="Car")> _
Public Class CarV2
<DataMember()> _
Private Model As String
<DataMember()> _
Private HorsePower As Integer
End Class
O ponto de extremidade da versão 2 pode enviar dados com êxito para o ponto de extremidade da versão 1. A serialização da versão 2 do contrato de dados Car
produz um XML semelhante ao seguinte.
<Car>
<Model>Porsche</Model>
<HorsePower>300</HorsePower>
</Car>
O mecanismo de desserialização na V1 não encontra um membro de dados correspondente para o campo HorsePower
e descarta esses dados.
Além disso, o ponto de extremidade da versão 1 pode enviar dados para o ponto de extremidade da versão 2. A serialização da versão 1 do contrato de dados Car
produz um XML semelhante ao seguinte.
<Car>
<Model>Porsche</Model>
</Car>
O desserializador da versão 2 não sabe como definir o campo HorsePower
porque não há dados correspondentes no XML de entrada. Em vez disso, o campo é definido com o valor padrão de 0.
Membros de dados obrigatórios
Um membro de dados pode ser marcado como obrigatório definindo a propriedade IsRequired de DataMemberAttribute como true
. Se os dados necessários estiverem ausentes durante a desserialização, uma exceção será lançada em vez de definir o membro de dados para seu valor padrão.
Adicionar um membro de dados obrigatório é uma alteração interruptiva. Ou seja, o tipo mais recente ainda pode ser enviado para pontos de extremidade com o tipo mais antigo, mas não o contrário. A remoção de um membro de dados que foi marcado como necessário em qualquer versão anterior também é uma alteração interruptiva.
Alterar o valor da propriedade IsRequired de true
para false
não é interruptivo, mas alterá-lo de false
para true
pode ser interruptivo se alguma versão anterior do tipo não tiver o membro de dados em questão.
Observação
Embora a propriedade IsRequired esteja definida como true
, os dados de entrada podem ser nulos ou zero, e um tipo deve ser preparado para lidar com essa possibilidade. Não use IsRequired como um mecanismo de segurança para proteção contra dados de entrada incorretos.
Valores padrão omitidos
É possível (embora não recomendado) definir a propriedade EmitDefaultValue
no atributo DataMemberAttribute como false
, conforme descrito em Valores padrão do membro de dados. Se esta configuração for false
, o membro de dados não será emitido se estiver definido com seu valor padrão (geralmente nulo ou zero). Isso não é compatível com membros de dados necessários em versões diferentes de duas maneiras:
Um contrato de dados com um membro de dados necessário em uma versão não pode receber dados padrão (nulos ou zero) de uma versão diferente na qual o membro de dados tenha
EmitDefaultValue
definido comofalse
.Um membro de dados obrigatório que tenha
EmitDefaultValue
definido comofalse
não pode ser usado para serializar seu valor padrão (nulo ou zero), mas pode receber tal valor na desserialização. Isso cria um problema de ida e volta (os dados podem ser lidos, mas os mesmos dados não podem ser gravados). Portanto, seIsRequired
fortrue
eEmitDefaultValue
forfalse
em uma versão, a mesma combinação deve ser aplicada a todas as outras versões, de modo que nenhuma versão do contrato de dados possa produzir um valor que não resulte em uma viagem de ida e volta.
Considerações do esquema
Para obter uma explicação de qual esquema é produzido para tipos de contrato de dados, consulte Referência de esquema de contrato de dados.
O esquema que o WCF produz para tipos de contrato de dados não faz provisões para controle de versão. Ou seja, o esquema exportado de uma determinada versão de um tipo contém apenas os membros de dados presentes nessa versão. A implementação da interface IExtensibleDataObject não altera o esquema de um tipo.
Os membros de dados são exportados para o esquema como elementos opcionais por padrão. Ou seja, o valor minOccurs
(atributo XML) é definido como 0. Os membros de dados necessários são exportados com minOccurs
definido como 1.
Muitas das mudanças consideradas não interruptiva estão, na verdade, é interruptiva se for necessária uma adesão estrita ao esquema. No exemplo anterior, uma instância CarV1
com apenas o elemento Model
seria validada em relação ao esquema CarV2
(que tem Model
e Horsepower
, mas ambos são opcionais). No entanto, o inverso não é verdadeiro: uma instância CarV2
falharia na validação em relação ao esquema CarV1
.
A viagem de ida e volta também envolve algumas considerações adicionais. Para obter mais informações, consulte a seção "Considerações do esquema" em Contratos de dados compatíveis com o futuro.
Outras alterações permitidas
A implementação da interface IExtensibleDataObject é uma mudança ininterrupta. No entanto, o suporte de ida e volta não existe para versões do tipo anteriores à versão em que IExtensibleDataObject foi implementado. Para obter mais informações, consulte Contratos de dados compatíveis por encaminhamento.
Enumerações
Adicionar ou remover um membro de enumeração é uma alteração interruptiva. A alteração do nome de um membro de enumeração está sendo interrompida, a menos que seu nome de contrato seja mantido igual ao da versão antiga usando o atributo EnumMemberAttribute
. Para obter mais informações, consulte Tipos de enumeração em contratos de dados.
Coleções
A maioria das alterações de coleção são ininterruptas porque a maioria dos tipos de coleção são intercambiáveis entre si no modelo de contrato de dados. No entanto, personalizar uma coleção não personalizada ou vice-versa é uma mudança interruptiva. Além disso, alterar as configurações de personalização da coleção é uma alteração interruptiva; ou seja, alterar o nome e o namespace do contrato de dados, repetindo o nome do elemento, o nome do elemento-chave e o nome do elemento de valor. Para obter mais informações sobre personalização de coleção, consulte Tipos de coleção em contratos de dados.
Naturalmente, alterar o contrato de dados do conteúdo de uma coleção (por exemplo, mudar de uma lista de inteiros para uma lista de cadeia de caracteres) é uma alteração interruptiva.
Confira também
- Name
- DataMemberAttribute
- Name
- Namespace
- Order
- IsRequired
- SerializationException
- IExtensibleDataObject
- Retornos de chamada de serialização tolerantes à versão
- Melhores práticas: Controle de versão de contrato de dados
- Usando contratos de dados
- Equivalência de contrato de dados
- Contratos de dados compatíveis por encaminhamento