Partager via


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

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

Processus d’importation

Le processus d’importation de schéma commence par un XmlSchemaSet et produit un CodeCompileUnit.

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

Il CodeCompileUnit fait partie du modèle objet de document de code (CodeDOM) 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 la classe VBCodeProvider.

Pour importer un schéma

  1. Créez une instance du XsdDataContractImporter.

  2. Optionnel. Passez un CodeCompileUnit dans le constructeur. Les types générés lors de l’importation de schéma sont ajoutés à cette CodeCompileUnit instance au lieu de commencer par un vide CodeCompileUnit.

  3. Optionnel. 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 CanImport méthode a les mêmes surcharges que Import (l’étape suivante).

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

    La surcharge la plus simple accepte XmlSchemaSet et importe tous les types, y compris les types anonymes, trouvés 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 du 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 y existe déjà. Appelez Import plusieurs fois sur le même XsdDataContractImporter réseau au lieu d’utiliser plusieurs XsdDataContractImporter objets. Il s’agit de la méthode recommandée pour éviter la génération de types en double.

    Remarque

    En cas de défaillance lors de l’importation, l’état CodeCompileUnit est imprévisible. L’utilisation d’une CodeCompileUnit issue d’un import échoué peut vous exposer à des vulnérabilités.

  5. Accédez au CodeCompileUnit via la CodeCompileUnit propriété.

Options d’importation : personnalisation des types générés

Vous pouvez définir la Options propriété de la XsdDataContractImporter classe sur une instance de la ImportOptions classe pour contrôler différents aspects du processus d’importation. Un certain nombre d’options influencent directement les types générés.

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

Cela correspond au commutateur /internal sur l’outil utilitaire de métadonnées ServiceModel (Svcutil.exe).

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

L’exemple suivant montre un schéma transformé en classe interne lorsque la GenerateInternal propriété est définie sur 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 option /namespace)

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

Normalement, les types générés à partir du schéma sont générés dans des espaces de noms .NET Framework, chaque espace de noms XSD correspondant à un espace de noms .NET Framework particulier en fonction d’un mappage décrit dans la référence de schéma du contrat de données. Vous pouvez personnaliser ce mappage en utilisant la propriété Namespaces vers Dictionary<TKey,TValue>. Si un espace de noms XSD donné se trouve dans le dictionnaire, l’espace de noms .NET Framework correspondant est également extrait de votre dictionnaire.

Par exemple, considérez 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 nommage 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 l'attribut Serializable (GenerateSerializable ou de l'option /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 l’activer, vous devez appliquer l’attribut SerializableAttribute aux types générés en plus de l’attribut normal DataContractAttribute . L’attribut est généré automatiquement si l’option GenerateSerializable d’importation est définie sur true.

L’exemple suivant montre la Vehicle classe générée avec l’option d’importation GenerateSerializable définie sur 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 à l'option /enableDataBinding dans l'outil Svcutil.exe.

Parfois, vous souhaiterez peut-être lier les types générés à partir du schéma aux composants d’interface utilisateur graphique afin que toute mise à jour de ces types met automatiquement à jour l’interface utilisateur. Le 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 de l'interface utilisateur client qui prend en charge cette interface (par exemple, Windows Presentation Foundation (WPF)), définissez la propriété EnableDataBinding sur true pour activer cette fonctionnalité.

L’exemple suivant montre la Vehicle classe générée avec le EnableDataBinding réglé sur 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 dans XML représentent des collections d’éléments : des listes d’éléments et des associations entre un élément et un autre. Voici un exemple de liste de chaînes.

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

Voici un exemple d’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>

Remarque

Toute association peut également être considérée comme une liste. Par exemple, vous pouvez afficher l’association précédente sous la forme d’une liste d’objets complexes city qui ont deux champs (un champ de chaîne et un champ entier). Les deux modèles ont une représentation dans le schéma XSD. Il n’existe aucun moyen de différencier une liste et une association. Ces modèles sont donc toujours traités comme des listes, sauf si une annotation spéciale spécifique à 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 en tant que contrat de données de collecte qui dérive d’une liste générique ou d’un tableau .NET Framework, selon que le schéma suit ou non le modèle d’affectation de noms standard pour les collections. Cette procédure est décrite plus en détail dans les types de collecte 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érez 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>

Cela serait importé comme suit (les champs sont affichés au lieu des propriétés pour la 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 dérivant de la BindingList<T> classe au lieu de la List<T> classe afin de lier le type à une zone de liste et de le mettre automatiquement à jour lorsque le contenu de la collection change. Pour ce faire, définissez la ReferencedCollectionTypes propriété de la ImportOptions classe sur une liste de types de collection à utiliser (appelées ci-après 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 la plus adaptée est utilisée si une collection est trouvée. Les associations sont mises en correspondance uniquement par rapport aux types qui implémentent l’interface générique ou non IDictionary générique, tandis que les listes sont mises en correspondance avec n’importe quel type de collection pris en charge.

Par exemple, si la ReferencedCollectionTypes propriété est définie sur un BindingList<T>, le people type de 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 trouvés dans le schéma sont importées en tant que BindingList(Of Integer). Toutes les autres listes, par exemple, un List(Of String), sont importées en tant que ArrayList.

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

Les doublons ne sont pas autorisés. Par exemple, vous ne pouvez pas ajouter à la fois un List(Of Integer) et un Collection(Of Integer) aux types référencés. Cela rend impossible de déterminer qui doit être utilisé lorsqu’une liste d’entiers est trouvée dans le schéma. Les doublons ne sont détectés que s’il existe un type dans le schéma qui expose le problème des doublons. Par exemple, si le schéma importé ne contient pas de listes d’entiers, il est permis d'inclure à la fois List(Of Integer) et Collection(Of Integer) dans la collection des types référencés, mais aucun des deux n’aura d’effet.

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

La ReferencedCollectionTypes propriété correspond au commutateur /collectionType sur l’outil SvcUtil.exe. Notez que pour référencer plusieurs types de collection, le commutateur /collectionType doit être spécifié plusieurs 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

Parfois, les types dans le schéma correspondent aux types .NET Framework existants et il n’est pas nécessaire de générer ces types à partir de zéro. (Cette section s’applique uniquement aux types non-collection. Pour les types de collection, consultez la section précédente.)

Par exemple, vous pouvez avoir un type standard de contrat de données « Personne » à l’échelle de l’entreprise que vous souhaitez toujours utiliser lors de la représentation d’une personne. Chaque fois que certains services utilisent ce type et que son schéma apparaît dans les métadonnées du service, vous pouvez réutiliser le type existant Person lors de l’importation de ce schéma au lieu de générer un nouveau pour chaque service.

Pour ce faire, transmettez une liste de types .NET Framework que vous souhaitez réutiliser dans la collection que la propriété ReferencedTypes de la classe ImportOptions retourne. Si l’un de ces types a un nom de contrat de données et un espace de noms correspondant 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 correspondants et des structures correspondantes, le type .NET Framework existant est réutilisé au lieu de générer un nouveau. Si seul le nom correspond mais pas la structure, une exception est levée. Notez qu’il n’existe aucune autorisation pour le contrôle de version lors du référencement de types (par exemple, l’ajout de nouveaux membres de données facultatifs). Les structures doivent correspondre exactement.

Il est légal d’ajouter plusieurs types avec le même nom de contrat de données et l’espace de noms à la collection de types référencés, tant qu’aucun type de schéma n’est importé avec ce nom et cet espace de noms. Cela vous permet d’ajouter facilement tous les types d’un assembly à la collection sans vous soucier des doublons pour les types qui ne se produisent pas réellement dans le schéma.

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

Remarque

Lors de l’utilisation de l'Svcutil.exe ou (dans Visual Studio) des outils Add Service Reference , tous les types de MsCorLib.dll sont automatiquement référencés.

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

Le 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, des attributs XML), la tentative d’importation échoue avec une exception. Toutefois, définir la propriété ImportXmlType sur true étend la gamme de schémas pris en charge. Lorsqu'il est défini à true, les types XsdDataContractImporter générés implémentent l'interface IXmlSerializable. Cela permet un accès direct à la représentation XML de ces types.

Considérations relatives à la conception
  • Il peut être difficile de travailler directement avec la représentation XML faiblement typée. Envisagez d’utiliser un autre moteur de sérialisation, tel que le XmlSerializer, pour travailler avec des schémas qui ne sont pas compatibles avec les contrats de données, de manière fortement typée. 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 est définie sur true. Là encore, envisagez d’utiliser le 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 générés IXmlSerializable ne conserve pas son exactitude lors de l'importation et de l'exportation. Autrement dit, l’exportation du schéma à partir des types générés et de l’importation en tant que classes ne retourne pas le schéma d’origine.

Il est possible de combiner l’option ImportXmlType avec l’option ReferencedTypes décrite précédemment. Pour les types qui doivent être générés en tant qu’implémentations IXmlSerializable , la vérification structurelle est ignorée lors de l’utilisation de la ReferencedTypes fonctionnalité.

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

Utilisation des types IXmlSerializable générés

Les types générés IXmlSerializable contiennent un champ privé nommé « nodeField », qui retourne un tableau d’objets XmlNode . Lors de la désérialisation d’une instance de ce type, vous pouvez accéder aux données XML directement via ce champ à l’aide du modèle objet document XML. Lors de la sérialisation d’une instance de ce type, vous pouvez définir ce champ sur les données XML souhaitées et il sera sérialisé.

Cela s’effectue par le biais de l’implémentation IXmlSerializable . Dans le type généré IXmlSerializable , l’implémentation ReadXml appelle la ReadNodes méthode de la XmlSerializableServices classe. La méthode est une fonction d'assistance qui convertit le XML fourni via un XmlReader en un tableau d'objets XmlNode. L’implémentation WriteXml effectue l’inverse et convertit le tableau d’objets XmlNode en une séquence d’appels XmlWriter . Cette opération est effectuée à l’aide de la WriteNodes méthode.

Il est possible d’exécuter le processus d’exportation de schéma sur les classes générées IXmlSerializable . Comme indiqué précédemment, vous n’obtiendrez pas le schéma d’origine. Au lieu de cela, vous obtiendrez le type XSD standard « anyType », qui est un caractère générique pour n’importe quel type XSD.

Pour ce faire, appliquez l’attribut XmlSchemaProviderAttribute aux classes générées IXmlSerializable et spécifiez une méthode qui appelle la AddDefaultSchema méthode pour générer le type « anyType ».

Remarque

Le XmlSerializableServices type existe uniquement pour prendre en charge cette fonctionnalité particulière. Il n’est pas recommandé d’utiliser à d’autres fins.

Options d’importation : Options avancées

Les options d’importation avancées suivantes sont les suivantes :

Voir aussi