Partager via


Contrôle de version des contrats de données

À mesure que les applications évoluent, vous devrez peut-être également modifier les contrats de données que les services utilisent. Cette rubrique explique comment versionr les contrats de données. Cette rubrique décrit les mécanismes de contrôle de version de contrat de données. Pour obtenir une vue d’ensemble complète et des conseils de contrôle de version prescriptifs, consultez Les meilleures pratiques : Gestion des versions de contrat de données.

Changements disruptifs et non disruptifs

Les modifications apportées à un contrat de données peuvent être avec ou sans rupture. Lorsqu’un contrat de données est modifié de manière insécable, une application utilisant l’ancienne version du contrat peut communiquer avec une application à l’aide de la version la plus récente, et une application utilisant la version la plus récente du contrat peut communiquer avec une application à l’aide de l’ancienne version. En revanche, une modification radicale bloque la communication dans une ou les deux directions.

Toute modification apportée à un type qui n'affecte pas la façon dont il est transmis et reçu est sans rupture. Ces modifications ne modifient pas le contrat de données, seul le type sous-jacent. Par exemple, vous pouvez modifier le nom d’un champ sans rupture si vous définissez ensuite la propriété Name de DataMemberAttribute sur le nom de la version antérieure. Le code suivant montre la version 1 d’un contrat de données.

// Version 1
[DataContract]
public class Person
{
    [DataMember]
    private string Phone;
}
' Version 1
<DataContract()> _
Public Class Person
    <DataMember()> _
    Private Phone As String
End Class

Le code suivant présente une modification sans rupture.

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

Certaines modifications affectent les données transmises, mais peuvent être avec ou sans rupture. Les modifications suivantes sont toujours avec rupture :

  • Modification de la valeur Name ou Namespace d’un contrat de données.

  • Modifier l'ordre des membres de données en utilisant la propriété Order du DataMemberAttribute.

  • Attribution d'un nouveau nom à un membre de données.

  • Modification du contrat de données d'un membre de données. Par exemple, en changeant le type de membre de données d’un entier en chaîne de caractères, ou d’un type avec un contrat de données nommé « Client » à un type avec un contrat de données nommé « Personne ».

Les modifications suivantes sont également possibles.

Ajout et suppression de membres de données

Dans la plupart des cas, l’ajout ou la suppression d’un membre de données n’est pas une modification cassante, sauf si vous avez besoin d’une validité stricte du schéma (de nouvelles instances valides par rapport à l’ancien schéma).

Lorsqu’un type avec un champ supplémentaire est désérialisé dans un type avec un champ manquant, les informations supplémentaires sont ignorées. (Il peut également être stocké à des fins d’aller-retour ; pour plus d’informations, consultez Forward-Compatible Contrats de données).

Lorsqu’un type avec un champ manquant est désérialisé dans un type avec un champ supplémentaire, le champ supplémentaire est laissé à sa valeur par défaut, généralement zéro ou null. (La valeur par défaut peut être modifiée ; pour plus d’informations, consultez Version-Tolerant Rappels de sérialisation.)

Par exemple, vous pouvez utiliser la CarV1 classe sur un client et la CarV2 classe sur un service, ou vous pouvez utiliser la CarV1 classe sur un service et la CarV2 classe sur un client.

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

Le point de terminaison version 2 peut envoyer des données au point de terminaison version 1. La sérialisation de la version 2 du Car contrat de données génère des données XML similaires à ce qui suit.

<Car>  
    <Model>Porsche</Model>  
    <HorsePower>300</HorsePower>  
</Car>  

Le moteur de désérialisation sur V1 ne trouve pas de membre de données correspondant pour le HorsePower champ et ignore ces données.

En outre, le point de terminaison version 1 peut envoyer des données au point de terminaison version 2. La sérialisation de la Car version 1 du contrat de données génère des données XML similaires à ce qui suit.

<Car>  
    <Model>Porsche</Model>  
</Car>  

Le désérialiseur version 2 ne sait pas sur quoi définir le HorsePower champ, car il n’existe aucune donnée correspondante dans le code XML entrant. Au lieu de cela, le champ est défini sur la valeur par défaut de 0.

Membres de données requis

Un membre de données peut être marqué comme requis en définissant la propriété IsRequired du DataMemberAttribute à true. Si les données requises sont manquantes lors de la désérialisation, une exception est levée au lieu de définir le membre de données sur sa valeur par défaut.

L’ajout d’un membre de données requis constitue un changement disruptif. Autrement dit, le type plus récent peut toujours être envoyé aux points de terminaison avec l’ancien type, mais pas de l’autre façon. La suppression d'un membre de données marqué comme étant requis dans les versions antérieures est également une modification avec rupture.

La modification de la valeur de la propriété IsRequired de true à false n'entraîne pas une rupture, mais la modification de la valeur de false à true peut entraîner une rupture si certaines versions antérieures du type ne possèdent pas le membre de données concerné.

Remarque

Bien que la propriété IsRequired soit définie sur true, les données entrantes peuvent être nulles ou égales à zéro, et il faut préparer un type pour gérer cette éventualité. N’utilisez IsRequired pas comme mécanisme de sécurité pour vous protéger contre les données entrantes incorrectes.

Valeurs par défaut omises

Il est possible (bien que non recommandé) de définir la propriété EmitDefaultValue sur l’attribut false DataMemberAttribute, comme décrit dans Valeurs par défaut des membres de données. Si ce paramètre est falsedéfini, le membre de données ne sera pas émis s’il est défini sur sa valeur par défaut (généralement null ou zéro). Cela n’est pas compatible avec les membres de données requis dans différentes versions de deux manières :

  • Un contrat de données avec un membre de données requis dans une version ne peut pas recevoir des données par défaut (null ou zéro) provenant d’une autre version où le membre de données est configuré sur EmitDefaultValue pour false.

  • Un membre de données obligatoire qui a EmitDefaultValue défini à false ne peut pas être utilisé pour sérialiser sa valeur par défaut (null ou zéro), mais peut recevoir une telle valeur lors de la désérialisation. Cela crée un problème d’aller-retour (les données peuvent être lues, mais les mêmes données ne peuvent pas ensuite être écrites). Par conséquent, si IsRequired est true et EmitDefaultValue est false dans une version, la même combinaison doit s'appliquer à toutes les autres versions de sorte qu'aucune version du contrat de données ne puisse produire une valeur qui ne résulte pas en un aller-retour conforme au contrat.

Considérations relatives au schéma

Pour obtenir une explication du schéma généré pour les types de contrats de données, consultez Informations de référence sur le schéma du contrat de données.

Le schéma WCF produit pour les types de contrats de données ne prend aucune disposition pour le contrôle de version. Autrement dit, le schéma exporté à partir d’une certaine version d’un type contient uniquement les membres de données présents dans cette version. L’implémentation de l’interface IExtensibleDataObject ne modifie pas le schéma d’un type.

Les membres de données sont exportés vers le schéma en tant qu’éléments facultatifs par défaut. Autrement dit, la minOccurs valeur (attribut XML) est définie sur 0. Les membres de données requis sont exportés avec minOccurs défini à 1.

Un grand nombre des modifications considérées comme étant sans rupture sont en fait avec rupture si une adhésion stricte au schéma est requise. Dans l’exemple précédent, une CarV1 instance avec seulement l’élément Model validerait par rapport au CarV2 schéma (qui a les deux Model et Horsepower, mais les deux sont facultatives). Toutefois, l’inverse n’est pas vrai : une CarV2 instance échouerait la validation sur le CarV1 schéma.

L’aller-retour implique également des considérations supplémentaires. Pour plus d’informations, consultez la section « Considérations relatives au schéma » dans Forward-Compatible contrats de données.

Autres modifications autorisées

L’implémentation de l’interface IExtensibleDataObject est une modification non disruptive. Toutefois, la prise en charge des allers-retours n’existe pas pour les versions du type antérieures à la version dans laquelle IExtensibleDataObject a été implémentée. Pour plus d’informations, consultez Forward-Compatible Contrats de données.

Énumérations

L'ajout ou la suppression d'un membre d'énumération est une modification avec rupture. La modification du nom d'un membre d'énumération est avec rupture, sauf si son nom de contrat est conservé en utilisant l'attribut EnumMemberAttribute. Pour plus d’informations, consultez Types d’énumération dans les contrats de données.

Collections

La plupart des modifications de collection sont sans rupture, car la plupart des types de collections sont interchangeables entre eux dans le modèle de contrat de données. Toutefois, rendre une collection non personnalisée personnalisée ou vice versa est un changement disruptif. De plus, la modification des paramètres de personnalisation de la collection est une modification avec rupture ; en d'autres termes, la modification de l'espace de noms et du nom de contrat de données, du nom d'élément répétitif, du nom d'élément clé et du nom d'élément de valeur. Pour plus d’informations sur la personnalisation de la collection, consultez Types de collecte dans les contrats de données.
Naturellement, la modification du contrat de données de contenu d’une collection (par exemple, la modification d’une liste d’entiers en une liste de chaînes) est une modification avec rupture.

Voir aussi