Importation du schéma pour générer des classes

Pour générer des classes à partir de schémas qui soient utilisables avec Windows Communication Foundation (WCF), utilisez la classe XsdDataContractImporter. Cette rubrique décrit le processus et les variations.

Processus d'importation

Le processus d'importation du schéma démarre avec un XmlSchemaSet et produit un CodeCompileUnit.

Le XmlSchemaSet fait partie du modèle SOM (Schema Object Model) du .NET Framework qui représente un jeu de documents de schéma XSD (XML Schema Definition Language). Pour créer un objet XmlSchemaSet à partir d'un jeu de documents XSD, désérialisez chaque document dans un objet XmlSchema (à l'aide du XmlSerializer) et ajoutez ces objets à un nouveau XmlSchemaSet.

Le CodeCompileUnit fait partie du modèle CodeDOM (Code Document Object Model) du .NET Framework qui représente le code .NET Framework de manière abstraite. Pour générer le code réel à partir d'un CodeCompileUnit, utilisez une sous-classe de la classe CodeDomProvider, telle que la classe CSharpCodeProvider ou VBCodeProvider.

Pour importer un schéma

  1. Créez une instance de XsdDataContractImporter.

  2. facultatif. Passez un CodeCompileUnit dans le constructeur. Les types générés pendant l'importation de schéma sont ajoutés à cette instance CodeCompileUnit au lieu de démarrer avec un CodeCompileUnit vide.

  3. facultatif. Appelez une des méthodes CanImport. La méthode détermine si le schéma donné est un schéma de contrat de données valide et peut être importé. La méthode CanImport a les mêmes surcharges que Import (étape suivante).

  4. Appelez l'une des méthodes Import surchargées, par exemple, la méthode Import(XmlSchemaSet).

    La surcharge la plus simple prend un XmlSchemaSet et importe tous les types, y compris les types anonymes figurant dans ce jeu de schémas. D'autres surcharges vous permettent de spécifier le type XSD ou une liste de types à importer (sous la forme d'un XmlQualifiedName ou d'une collection d'objets XmlQualifiedName). Dans ce cas, seuls les types spécifiés sont importés. Une surcharge prend un XmlSchemaElement qui importe un élément particulier hors du XmlSchemaSet, ainsi que son type associé (qu'il soit anonyme ou non). Cette surcharge retourne un XmlQualifiedName qui représente le nom de contrat de données du type généré pour cet élément.

    Plusieurs appels de la méthode Import entraînent l'ajout de plusieurs éléments au même CodeCompileUnit. Un type n'est pas généré dans le CodeCompileUnit s'il est déjà présent. Appelez plusieurs fois Import sur le même XsdDataContractImporter au lieu d'utiliser plusieurs objets XsdDataContractImporter. Il s'agit de la méthode recommandée pour éviter de générer des types en double.

    Notes

    En cas de défaillance pendant l'importation, le CodeCompileUnit sera dans un état imprévisible. L'utilisation d'un CodeCompileUnit issu d'une importation ayant échoué pourrait vous exposer à des failles de sécurité.

  5. Accédez à CodeCompileUnit à l'aide de la propriété CodeCompileUnit .

Option d'importation : personnalisation des types générés

Vous pouvez définir la propriété Options de XsdDataContractImporter avec une instance de la classe ImportOptions pour contrôler divers aspect du processus d'importation. Plusieurs options influencent directement les types générés.

Contrôle du niveau d'accès (GenerateInternal ou le commutateur /internal)

Cela correspond au commutateur /internal dans l’outil ServiceModel Metadata Utility Tool (Svcutil.exe).

Normalement, les types publics sont générés à partir d'un schéma, avec les champs privés et les propriétés de membres de données publiques correspondantes. Pour générer des types internes à la place, affectez à la propriété GenerateInternal la valeur true.

L'exemple suivant affiche un schéma transformé en une classe interne lorsque la propriété GenerateInternal a la valeur true.

[DataContract]
internal partial class Vehicle : IExtensibleDataObject
{
    private int yearField;
    private string colorField;

    [DataMember]
    internal int year
    {
        get { return this.yearField; }
        set { this.yearField = value; }
    }
    [DataMember]
    internal string color
    {
        get { return this.colorField; }
        set { this.colorField = value; }
    }

    private ExtensionDataObject extensionDataField;
    public ExtensionDataObject ExtensionData
    {
        get { return this.extensionDataField; }
        set { this.extensionDataField = value; }
    }
}
Class Vehicle
    Implements IExtensibleDataObject
    Private yearField As Integer
    Private colorField As String

    <DataMember()> _
    Friend Property year() As Integer
        Get
            Return Me.yearField
        End Get
        Set
            Me.yearField = value
        End Set
    End Property

    <DataMember()> _
    Friend Property color() As String
        Get
            Return Me.colorField
        End Get
        Set
            Me.colorField = value
        End Set
    End Property
    Private extensionDataField As ExtensionDataObject

    Public Property ExtensionData() As ExtensionDataObject _
        Implements IExtensibleDataObject.ExtensionData
        Get
            Return Me.extensionDataField
        End Get
        Set(ByVal value As ExtensionDataObject)
            Me.extensionDataField = value
        End Set
    End Property
End Class

Contrôle des espaces de noms (espaces de noms ou le commutateur /namespace)

Cela correspond au commutateur /namespace dans l’outil Svcutil.exe.

Normalement, les types générés à partir d’un schéma sont générés dans des espaces de noms du .NET Framework, et chaque espace de noms XSD correspond à un espace de noms particulier du .NET Framework selon un mappage décrit dans Référence des schémas de contrats de données. Vous pouvez personnaliser ce mappage par la propriété Namespaces à un Dictionary<TKey,TValue>. Si un espace de noms XSD particulier provient du dictionnaire, l'espace de noms correspondant du .NET Framework est également issu de votre dictionnaire.

Par exemple, considérons le schéma suivant.

<xs:schema targetNamespace="http://schemas.contoso.com/carSchema">
  <xs:complexType name="Vehicle">
    <!-- details omitted... -->
  </xs:complexType>
</xs:schema>

L'exemple suivant utilise la propriété Namespaces pour mapper l'espace de noms http://schemas.contoso.com/carSchema à « Contoso.Cars ».

XsdDataContractImporter importer = new XsdDataContractImporter();
importer.Options.Namespaces.Add(new KeyValuePair<string, string>("http://schemas.contoso.com/carSchema", "Contoso.Cars"));
Dim importer As New XsdDataContractImporter
importer.Options.Namespaces.Add(New KeyValuePair(Of String, String)("http://schemas.contoso.com/carSchema", "Contoso.Cars"))

Ajout de SerializableAttribute (GenerateSerializable ou le commutateur /serializable)

Cela correspond au commutateur /serializable dans l’outil Svcutil.exe.

Il est parfois important que les types générés à partir du schéma soient utilisables avec les moteurs de sérialisation du runtime .NET Framework. Cela est utile si vous utilisez des types pour la communication à distance .NET Framework. Pour ce faire, vous devez appliquer l'attribut SerializableAttribute aux types générés en plus de l'attribut DataContractAttribute standard. L'attribut est généré automatiquement si l'option d'importation GenerateSerializable a la valeur true.

L'exemple suivant affiche la classe Vehicle générée lorsque l'option d'importation GenerateSerializable a la valeur true.

[DataContract]
[Serializable]
public partial class Vehicle : IExtensibleDataObject
{
    // Code not shown.
    public ExtensionDataObject ExtensionData
    {
        get
        {
            throw new Exception("The method or operation is not implemented.");
        }
        set
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}
<DataContract(), Serializable()> _
Partial Class Vehicle
    Implements IExtensibleDataObject
    Private extensionDataField As ExtensionDataObject

    ' Code not shown.

    Public Property ExtensionData() As ExtensionDataObject _
        Implements IExtensibleDataObject.ExtensionData
        Get
            Return Me.extensionDataField
        End Get
        Set(ByVal value As ExtensionDataObject)
            Me.extensionDataField = value
        End Set
    End Property

End Class

Ajout de la prise en charge de la liaisons de données (EnableDataBinding ou le commutateur /enableDataBinding)

Cela correspond au commutateur /enableDataBinding dans l’outil Svcutil.exe.

Il est parfois utile de lier les types générés à partir du schéma à des composants d'interface utilisateur graphique afin que toute mise à jour des instances de ces types mette à jour automatiquement l'interface utilisateur. XsdDataContractImporter peut générer des types qui implémentent l'interface INotifyPropertyChanged afin que toute modification de propriété déclenche un événement. Si vous générez des types à utiliser avec un environnement de programmation d’interface utilisateur client qui prend en charge cette interface (comme Windows Presentation Foundation, ou WPF), définissez la propriété EnableDataBinding sur true pour activer cette fonctionnalité.

L'exemple suivant affiche la classe Vehicle générée lorsque EnableDataBinding a la valeur true.

[DataContract]
public partial class Vehicle : IExtensibleDataObject, INotifyPropertyChanged
{
    private int yearField;
    private string colorField;

    [DataMember]
    public int year
    {
        get { return this.yearField; }
        set
        {
            if (this.yearField.Equals(value) != true)
            {
                this.yearField = value;
                this.RaisePropertyChanged("year");
            }
        }
    }
    [DataMember]
    public string color
    {
        get { return this.colorField; }
        set
        {
            if (this.colorField.Equals(value) != true)
            {
                this.colorField = value;
                this.RaisePropertyChanged("color");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler propertyChanged =
this.PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this,
new PropertyChangedEventArgs(propertyName));
        }
    }

    private ExtensionDataObject extensionDataField;
    public ExtensionDataObject ExtensionData
    {
        get { return this.extensionDataField; }
        set { this.extensionDataField = value; }
    }
}
Partial Class Vehicle
    Implements IExtensibleDataObject, INotifyPropertyChanged
    Private yearField As Integer
    Private colorField As String

    <DataMember()> _
    Public Property year() As Integer
        Get
            Return Me.yearField
        End Get
        Set
            If Me.yearField.Equals(value) <> True Then
                Me.yearField = value
                Me.RaisePropertyChanged("year")
            End If
        End Set
    End Property

    <DataMember()> _
    Public Property color() As String
        Get
            Return Me.colorField
        End Get
        Set
            If Me.colorField.Equals(value) <> True Then
                Me.colorField = value
                Me.RaisePropertyChanged("color")
            End If
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler _
      Implements INotifyPropertyChanged.PropertyChanged

    Private Sub RaisePropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, _
         New PropertyChangedEventArgs(propertyName))
    End Sub

    Private extensionDataField As ExtensionDataObject

    Public Property ExtensionData() As ExtensionDataObject _
        Implements IExtensibleDataObject.ExtensionData
        Get
            Return Me.extensionDataField
        End Get
        Set(ByVal value As ExtensionDataObject)
            Me.extensionDataField = value
        End Set
    End Property

End Class

Options d’importation : choix des types de collection

Deux modèles spéciaux au format XML représentent des collections d’éléments : les listes d’éléments et les associations entre un élément et un autre. Les éléments suivants sont un exemple d'une liste de chaînes.

<People>
  <person>Alice</person>
  <person>Bob</person>
  <person>Charlie</person>
</People>

Les éléments suivants sont un exemple d'une association entre une chaîne et un entier (city name et population).

<Cities>
  <city>
    <name>Auburn</name>
    <population>40000</population>
  </city>
  <city>
    <name>Bellevue</name>
    <population>80000</population>
  </city>
  <city>
    <name>Cedar Creek</name>
    <population>10000</population>
  </city>
</Cities>

Notes

Toute association peut aussi être considérée comme une liste. Par exemple, vous pouvez considérer l'association précédente comme une liste d'objets city complexes qui contiennent deux champs (un champ de chaîne et un champ d'entier). Les deux modèles sont représentés dans le schéma XSD. Il n’est pas possible de distinguer une liste et une association. Ces modèles sont donc toujours traités comme des listes, sauf si une annotation spéciale propre à WCF est présente dans le schéma. L’annotation indique qu’un modèle donné représente une association. Pour plus d’informations, consultez Référence des schémas de contrats de données.

Normalement, une liste est importée sous la forme d'un contrat de données de collection qui dérive d'une liste générique ou sous la forme d'un tableau du .NET Framework, selon que le schéma adopte ou non le modèle de nommage standard pour les collections. Cela est décrit plus en détail dans Types de collections dans les contrats de données. Les associations sont importées normalement comme Dictionary<TKey,TValue> ou comme un contrat de données de collection qui dérive de l'objet dictionnaire. Par exemple, considérons le schéma suivant.

<xs:complexType name="Vehicle">
  <xs:sequence>
    <xs:element name="year" type="xs:int"/>
    <xs:element name="color" type="xs:string"/>
    <xs:element name="passengers" type="people"/>
  </xs:sequence>
</xs:complexType>
<xs:complexType name="people">
  <xs:sequence>
    <xs:element name="person" type="xs:string" maxOccurs="unbounded" />
  </xs:sequence>
</xs:complexType>

Celui-ci serait importé comme suit (les champs sont montrés au lieu des propriétés à des fin de lisibilité).

[DataContract]
public partial class Vehicle : IExtensibleDataObject
{
    [DataMember] public int yearField;
    [DataMember] public string colorField;
    [DataMember] public people passengers;

    // Other code not shown.

    public ExtensionDataObject ExtensionData
    {
        get
        {
            throw new Exception("The method or operation is not implemented.");
        }
        set
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}
[CollectionDataContract(ItemName = "person")]
public class people : List<string> { }
Public Partial Class Vehicle
    Implements IExtensibleDataObject

    <DataMember()> _
    Public yearField As Integer
    <DataMember()> _
    Public colorField As String
    <DataMember()> _
    Public passengers As people

    ' Other code not shown.

    Public Property ExtensionData() As ExtensionDataObject _
    Implements IExtensibleDataObject.ExtensionData
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
        Set
            Throw New Exception("The method or operation is not implemented.")
        End Set
    End Property
End Class

<CollectionDataContract(ItemName:="person")> _
Public Class people
    Inherits List(Of String)
End Class

Il est possible de personnaliser les types de collection générés pour ces modèles de schéma. Par exemple, vous pouvez générer des collections qui dérivent de BindingList<T> au lieu de la classe List<T> afin de lier le type à une zone de liste et qu'il soit mis à jour automatiquement si le contenu de la collection est modifié. Pour cela, affectez à la propriété ReferencedCollectionTypes de la classe ImportOptions une liste de types de collection à utiliser (définis ci-dessous comme les types référencés). Lors de l’importation d’une collection, cette liste de types de collection référencés est analysée et la collection qui correspond le mieux est utilisée, s’il en existe une. Les associations sont mappées uniquement aux types qui implémentent l'interface générique ou non générique IDictionary, tandis que les listes sont mappées à tout type de collection pris en charge.

Par exemple, si la propriété ReferencedCollectionTypes a la valeur d'un BindingList<T>, le type people dans l'exemple précédent est généré comme suit.

[CollectionDataContract(ItemName = "person")]
public class people : BindingList<string> { }
<CollectionDataContract(ItemName:="person")> _
Public Class people
    Inherits BindingList(Of String)

Un type générique fermé est considéré comme la meilleure correspondance. Par exemple, si les types BindingList(Of Integer) et ArrayList sont passés à la collection de types référencés, toutes les listes d'entiers présentes dans le schéma sont importées comme BindingList(Of Integer). Toutes les autres listes, par exemple List(Of String), sont importées comme ArrayList.

Si un type qui implémente l'interface IDictionary générique est ajouté à la collection de types référencés, ses paramètres de type doivent être complètement ouverts ou complètement fermés.

Les doublons ne sont pas autorisés. Par exemple, vous ne pouvez pas ajouter à la fois List(Of Integer) et Collection(Of Integer) aux types référencés. Il serait alors impossible de déterminer lequel doit être utilisé lorsqu'une liste d'entiers est présente dans le schéma. Les doublons sont détectés uniquement si un type présent dans le schéma expose le problème des doublons. Par exemple, si le schéma importé ne contient pas de listes d’entiers, il est possible que la collection de types référencés contiennent à la fois List(Of Integer) et Collection(Of Integer), mais aucun n’aura d’effet.

Le mécanisme des types de collection référencés fonctionne également bien pour les collections de types complexes (y compris les collections d’autres collections), et pas seulement pour les collections de primitives.

La propriété ReferencedCollectionTypes correspond au commutateur /collectionType dans l’outil SvcUtil.exe. Notez que pour référencer plusieurs types de collection, le commutateur /collectionType doit être spécifié autant de fois. Si le type ne figure pas dans le fichier MsCorLib.dll, son assembly doit être aussi référencé à l’aide du commutateur /reference.

Options d'importation : référencement de types existants

Il arrive que des types dans le schéma correspondent à des types du .NET Framework existants. Il n'est alors pas nécessaire de générer ces types à partir de zéro. (Cette section s’applique uniquement aux types autres que les types de collection. Pour les types de collection, consultez la section précédente.)

Par exemple, vous avez peut-être un type standard de contrat de données « Person » dans l'entreprise que vous souhaitez toujours utiliser pour représenter une personne. Chaque fois que certains services font appel à ce type, et que son schéma apparaît dans les métadonnées de service, vous pouvez réutiliser le type Person existant lorsque vous importez ce schéma au lieu d'en générer un nouveau pour chaque service.

Pour cela, passez une liste des types du .NET Framework que vous souhaitez réutiliser dans la collection que la propriété ReferencedTypes retourne sur la classe ImportOptions. Si chacun de ces types a un nom et un espace de noms de contrat de données qui correspondent au nom et à l'espace de noms d'un type de schéma, une comparaison structurelle est effectuée. S'il est déterminé que les types ont à la fois des noms et des structures correspondants, le type du .NET Framework existant est réutilisé au lieu qu’un nouveau soit généré. Si seul le nom correspond mais pas la structure, une exception est levée. Notez qu'il n'y a aucune ressource pour le suivi des versions lors du référencement des types (par exemple, si vous ajoutez des membres de données facultatifs). Les structures doivent correspondre exactement.

Il est permis d’ajouter plusieurs types avec le même nom et espace de noms de contrat de données à la collection de types référencée, tant qu’aucun type de schéma n’est importé avec ce nom et cet espace de noms. Vous pouvez ainsi ajouter facilement tous les types dans un assembly à la collection sans tenir compte des doublons pour les types qui n’apparaissent pas réellement dans le schéma.

La propriété ReferencedTypes correspond au commutateur /reference dans certains modes de fonctionnement de l’outil Svcutil.exe.

Notes

Lorsque vous utilisez Svcutil.exe ou (dans Visual Studio) les outils Ajouter une référence de service, tous les types dans MsCorLib.dll sont référencés automatiquement.

Options d'importation : importer le schéma non DataContract comme types IXmlSerializable

XsdDataContractImporter prend en charge un sous-ensemble limité du schéma. Si des constructions de schéma non prises en charge sont présentes (par exemple, les attributs XML), la tentative d'importation échoue et génère une exception. Cependant, affecter à la propriété ImportXmlType la valeur true étend la plage de schéma prise en charge. Une fois qu'elle a la valeur true, la propriété XsdDataContractImporter génère des types qui implémentent l'interface IXmlSerializable. Cette opération permet d'accéder directement à la représentation XML de ces types.

Remarques sur la conception
  • Il peut s'avérer difficile d'utiliser directement la représentation XML faiblement typée. Vous pouvez faire appel à un moteur de sérialisation différent, tel que XmlSerializer, pour utiliser d'une manière fortement typée un schéma incompatible avec les contrats de données. Pour plus d’informations consultez Utilisation de la classe XmlSerializer.

  • Certaines constructions de schéma ne peuvent pas être importées par XsdDataContractImporter même lorsque la propriété ImportXmlType a la valeur true. Là encore, utilisez XmlSerializer pour ces cas.

  • Les constructions de schéma exactes qui sont prises en charge lorsque ImportXmlType a la valeur true ou false sont décrites dans Référence des schémas de contrats de données.

  • Le schéma pour les types IXmlSerializable générés ne conservent pas le respect lors de l'importation ou de l'exportation. Autrement dit, l'exportation du schéma à partir des types générés et son importation sous la forme de classes ne retournent pas le schéma d'origine.

Il est possible d'associer l'option ImportXmlType à l'option ReferencedTypes décrite précédemment. Pour les types qui doivent être générés comme des implémentations IXmlSerializable, le contrôle structurel est ignoré lors de l'utilisation de la fonctionnalité ReferencedTypes.

L’option ImportXmlType correspond au commutateur /importXmlTypes dans l’outil Svcutil.exe.

Utilisation des types IXmlSerializable générés

Les types IXmlSerializable générés contiennent un champ privé, nommé « nodesField », qui retourne un tableau d'objets XmlNode. Lorsque vous désérialisez une instance de ce type, vous pouvez accéder directement aux données XML par l'intermédiaire de ce champ en utilisant le modèle objet de document XML. Lorsque vous sérialisez une instance de ce type, vous pouvez affecter à ce champ les données XML souhaitées pour qu'il soit sérialisé.

Cette opération est possible grâce à l'implémentation IXmlSerializable. Dans le type IXmlSerializable généré, l'implémentation ReadXml appelle la méthode ReadNodes de la classe XmlSerializableServices. La méthode est une méthode d'assistance qui convertit le code XML fournie par XmlReader sous la forme d'un tableau d'objets XmlNode. L'implémentation WriteXml effectue l'opération inverse et convertit le tableau d'objets XmlNode sous la forme d'une séquence d'appels XmlWriter. Cette opération est possible grâce à la méthode WriteNodes.

Il est possible d'exécuter le processus d'exportation de schéma sur les classes IXmlSerializable générées. Comme indiqué précédemment, le schéma d'origine n'est pas récupéré. À la place, vous obtenez le type XSD standard « anyType », qui est un caractère générique pour tout type XSD.

Cela est possible en appliquant l'attribut XmlSchemaProviderAttribute aux classes IXmlSerializable générées et en spécifiant une méthode qui appelle la méthode AddDefaultSchema pour générer le type « anyType ».

Notes

Le type XmlSerializableServices est destiné uniquement à la prise en charge de cette fonctionnalité particulière. Il n'est pas recommandé de l'utiliser à d'autres fins.

Options d'importation : options avancées

Les éléments suivants représentent les options d'importation avancées :

Voir aussi