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
Créez une instance de XsdDataContractImporter.
facultatif. Passez un
CodeCompileUnit
dans le constructeur. Les types générés pendant l'importation de schéma sont ajoutés à cette instanceCodeCompileUnit
au lieu de démarrer avec unCodeCompileUnit
vide.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 queImport
(étape suivante).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'objetsXmlQualifiedName
). Dans ce cas, seuls les types spécifiés sont importés. Une surcharge prend un XmlSchemaElement qui importe un élément particulier hors duXmlSchemaSet
, ainsi que son type associé (qu'il soit anonyme ou non). Cette surcharge retourne unXmlQualifiedName
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êmeCodeCompileUnit
. Un type n'est pas généré dans leCodeCompileUnit
s'il est déjà présent. Appelez plusieurs foisImport
sur le mêmeXsdDataContractImporter
au lieu d'utiliser plusieurs objetsXsdDataContractImporter
. 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'unCodeCompileUnit
issu d'une importation ayant échoué pourrait vous exposer à des failles de sécurité.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
oufalse
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 :
Propriété CodeProvider. Spécifiez la CodeDomProvider à utiliser pour générer le code destiné aux classes générées. Le mécanisme d’importation cherche à éviter les fonctionnalités que la CodeDomProvider ne prend pas en charge. Si la propriété CodeProvider n'est pas définie, l’ensemble complet des fonctionnalités du .NET Framework est utilisé sans restrictions.
Propriété DataContractSurrogate. Une implémentation IDataContractSurrogate peut être spécifiée avec cette propriété. IDataContractSurrogate personnalise le processus d'importation. Pour plus d’informations, consultez Substituts de contrats de données. Par défaut, aucun substitut n'est utilisé.