Importation du schéma pour générer des classes
Pour générer des classes à partir des schémas qui sont 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 objet XmlSchemaSet et produit un objet CodeCompileUnit.
Le XmlSchemaSet est une partie du modèle SOM (Schema Object Model) du .NET Framework qui représente un jeu de documents de schéma XSD (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 de l'objet XmlSerializer) et ajoutez ces objets à un nouveau XmlSchemaSet.
Le CodeCompileUnit appartient au modèle CodeDOM (Code Document Object Model) du .NET Framework qui présente le code du .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 instance CodeCompileUnit au lieu de démarrer avec un CodeCompileUnit 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 que Import (étape suivante).
Appelez l'une des méthodes Import surchargées, par exemple, la méthode Import.
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.
Remarque : 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é. 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 sur le Outil Service Model Metadata 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.
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
[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;}
}
}
Contrôle des espaces de noms (espaces de noms ou le commutateur /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 du .NET Framework, et chaque espace de noms XSD correspond à un espace de noms du .NET Framework particulier 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. Si un espace de noms XSD donné figure dans le dictionnaire, l'espace de noms du .NET Framework correspondant 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 ».
Namespace Contoso.Cars
Class Vehicle
Implements IExtensibleDataObject
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
End Namespace
namespace Contoso.Cars {
[DataContract]
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.");
}
}
}
Ajout de SerializableAttribute (GenerateSerializable ou le commutateur /serializable)
Cela correspond au commutateur /serializable sur 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 la sérialisation de l'exécution du .NET Framework (par exemple, les classes BinaryFormatter et SoapFormatter). Cela est utile lors de l'utilisation de types pour .NET Framework Remoting. 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()> _
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
[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.");
}
}
}
Ajout de la prise en charge de la liaisons de données (EnableDataBinding ou le commutateur /enableDataBinding)
Cela correspond au commutateur /enableDataBinding sur 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 (tel que Windows Presentation Foundation (WPF)), affectez à la propriété EnableDataBinding la valeur true pour activer cette fonction.
L'exemple suivant affiche la classe Vehicle
générée lorsque EnableDataBinding a la valeur true.
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
[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;}
}
}
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>
Remarque : |
---|
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, donc ces modèles sont 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 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 si le schéma adopte ou non le modèle de désignation standard pour les collections. Cet exemple est décrit de manière plus détaillé dans Types de collections dans les contrats de données. Les associations sont importées normalement comme Dictionary 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é).
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
[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> {}
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 au lieu de la classe List 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, le type people
dans l'exemple précédent est généré comme suit.
<CollectionDataContract(ItemName := "person")> _
Public Class people
Inherits BindingList(Of String)
[CollectionDataContract(ItemName = "person")]
public class people : BindingList<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 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 n'est 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 et qu'il n'est pas nécessaire de générer ces types à partir de zéro. (Cette section s'applique uniquement aux types qui ne sont pas des collections. 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 d'en générer un nouveau. 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.
Remarque : |
---|
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.
Considérations de design
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 exactes de schéma 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 sur 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 obtiendrez le type XSD standard « anyType » qui est un caractère générique pour tout type XSD.
Cette opération 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 ».
Remarque : |
---|
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. Par exemple, le langage J# ne prend pas en charge de types génériques. Si vous spécifiez le fournisseur de code J# dans cette propriété, aucun type générique n'est généré dans le CodeCompileUnitde l'importateur. Si la propriété CodeProvider n'est pas définie, le jeu 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é.
Voir aussi
Référence
DataContractSerializer
XsdDataContractImporter
XsdDataContractExporter
ImportOptions
Concepts
Référence des schémas de contrats de données
Substituts de contrats de données
Importation et exportation de schémas
Exportation de schémas à partir de classes
Référence des schémas de contrats de données