Importazione dello schema per generare classi
Per generare classi da schemi utilizzabili con Windows Communication Foundation (WCF), usare la classe XsdDataContractImporter. In questo argomento viene descritto il processo e le relative varianti.
Processo di importazione
Il processo di importazione dello schema ha inizio con una classe XmlSchemaSet e produce una classe CodeCompileUnit.
La classe XmlSchemaSet
fa parte del modello di oggetti dello schema di .NET Framework che rappresenta un set di documenti dello schema XSD (XML Schema Definition Language). Per creare un oggetto XmlSchemaSet
da un set di documenti XSD, deserializzare ogni documento in un oggetto XmlSchema (mediante XmlSerializer) e aggiungere questi oggetti a una nuova classe XmlSchemaSet
.
La classe CodeCompileUnit
fa parte del CodeDOM (Code Document Object Model) di .NET Framework che rappresenta il codice .NET Framework in modo astratto. Per generare il codice effettivo da una classe CodeCompileUnit
, usare una sottoclasse della classe CodeDomProvider, ad esempio la classe CSharpCodeProvider o VBCodeProvider.
Per importare uno schema
Creare un'istanza di XsdDataContractImporter.
Facoltativo. Passare
CodeCompileUnit
nel costruttore. I tipi generati durante l'importazione dello schema vengono aggiunti a questa istanza diCodeCompileUnit
anziché iniziare con un'istanzaCodeCompileUnit
vuota.Facoltativo. Chiamare uno dei metodi CanImport. Il metodo determina se lo schema specificato è un schema del contratto dati valido e può essere importato. Il metodo
CanImport
presenta gli stessi overload diImport
(il passaggio successivo).Chiamare uno dei metodi
Import
di overload, ad esempio il metodo Import(XmlSchemaSet).L'overload più semplice accetta una classe
XmlSchemaSet
e importa tutti i tipi, incluso quelli anonimi, presenti in tale set di schemi. Gli altri overload consentono di specificare il tipo XSD o un elenco di tipi da importare (in una classe XmlQualifiedName o una raccolta di oggettiXmlQualifiedName
). In questo caso, vengono importati solo i tipi specificati. Un overload accetta una classe XmlSchemaElement che importa un elemento specifico fuori dalla classeXmlSchemaSet
, insieme al tipo associato (indipendentemente dal fatto che sia anonimo o meno). Questo overload restituisce una classeXmlQualifiedName
che rappresenta il nome del contratto dati del tipo generato per questo elemento.Più chiamate al metodo
Import
determinano l'aggiunta di più elementi alla stessa classeCodeCompileUnit
. Se esiste già un tipo inCodeCompileUnit
, non ne viene generato un altro. È consigliabile chiamare più volteImport
sullo stessoXsdDataContractImporter
anziché usare più oggettiXsdDataContractImporter
. Questa procedura è consigliata per evitare la generazione di tipi duplicati.Nota
Se si verifica un errore durante l'importazione, lo stato della classe
CodeCompileUnit
sarà imprevedibile. Se si usa una classeCodeCompileUnit
da un'importazione non riuscita, è possibile esporre il sistema a vulnerabilità della protezione.Accedere a
CodeCompileUnit
mediante la proprietà CodeCompileUnit .
Opzioni di importazione: personalizzazione dei tipi generati
È possibile impostare la proprietà Options di XsdDataContractImporter su un'istanza della classe ImportOptions per controllare vari aspetti del processo di importazione. Alcune opzioni influenzano direttamente i tipi generati.
Controllo del livello di accesso (opzione GenerateInternal o /internal)
Corrisponde all'opzione /internal dello strumento ServiceModel Metadata Utility Tool (Svcutil.exe).
In genere, i tipi pubblici vengono generati dallo schema, con campi privati e proprietà dei membri dati pubblici corrispondenti. Per generare invece tipi interni, impostare la proprietà GenerateInternal su true
.
Nell'esempio seguente viene illustrato uno schema trasformato in una classe interna quando la proprietà GenerateInternal è impostata su 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
Controllo degli spazi dei nomi (opzione Namespaces o /namespace)
Corrisponde all'opzione /namespace dello strumento Svcutil.exe
.
In genere, i tipi dallo schema vengono generati in spazi dei nomi .NET Framework, con ogni spazio dei nomi XSD corrispondente a uno spazio dei nomi .NET Framework specifico in base a un mapping descritto in Riferimento allo schema del contratto di dati. È possibile personalizzare questo mapping impostando la proprietà Namespaces su Dictionary<TKey,TValue>. Se uno specifico spazio dei nomi XSD viene rilevato nel dizionario, anche lo spazio dei nomi .NET Framework corrispondente viene prelevato dal dizionario.
Si consideri ad esempio lo schema seguente.
<xs:schema targetNamespace="http://schemas.contoso.com/carSchema">
<xs:complexType name="Vehicle">
<!-- details omitted... -->
</xs:complexType>
</xs:schema>
Nell'esempio seguente viene usata la proprietà Namespaces
per eseguire il mapping dello spazio dei nomi http://schemas.contoso.com/carSchema
a "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"))
Aggiunta di SerializableAttribute (opzione GenerateSerializable o /serializable)
Corrisponde all'opzione /serializable dello strumento Svcutil.exe
.
In alcuni casi può essere importante che i tipi generati dallo schema possano essere usati con i motori di serializzazione di runtime di .NET Framework. Questo aspetto è utile quando si usano tipi per i servizi remoti .NET Framework. Per abilitare questo aspetto è necessario applicare l'attributo SerializableAttribute ai tipi generati in aggiunta all'attributo DataContractAttribute normale. L'attributo viene generato automaticamente se l'opzione di importazione GenerateSerializable
è impostata su true
.
Nell'esempio seguente viene illustrata la classe Vehicle
generata con l'opzione di importazione GenerateSerializable
impostata su 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
Aggiunta del supporto dei data binding (opzione EnableDataBinding o /enableDataBinding)
Corrisponde all'opzione /enableDataBinding dello strumento Svcutil.exe.
In alcuni casi può essere necessario associare i tipi generati dallo schema ai componenti dell'interfaccia utente grafica in modo che qualsiasi aggiornamento alle istanze di questi tipi aggiorni automaticamente l'interfaccia utente. La classe XsdDataContractImporter
può generare tipi che implementano l'interfaccia INotifyPropertyChanged in modo che qualsiasi modifica delle proprietà generi un evento. Se si generano tipi da usare con un ambiente di programmazione dell'interfaccia utente client che supporta questa interfaccia (ad esempio Windows Presentation Foundation (WPF)), impostare la proprietà EnableDataBinding su true
per abilitare questa funzionalità.
Nell'esempio seguente viene illustrata la classe Vehicle
generata con la proprietà EnableDataBinding impostata su 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
Opzioni di importazione: scelta dei tipi di raccolta
Due modelli speciali in XML rappresentano raccolte di elementi: elenchi di elementi e associazioni tra due elementi. Di seguito è riportato un esempio di un elenco di stringhe.
<People>
<person>Alice</person>
<person>Bob</person>
<person>Charlie</person>
</People>
Nell'esempio seguente viene mostrata un'associazione tra una stringa e un numero intero (city name
e 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>
Nota
Qualsiasi associazione può essere considerata un elenco. Ad esempio, è possibile visualizzare l'associazione precedente come un elenco di oggetti city
complessi che presentano due campi (un campo stringa e un campo numero intero). Entrambi i modelli sono rappresentati nello schema XSD. Non è possibile differenziare un elenco da un'associazione, pertanto tali modelli vengono sempre considerati come elenchi a meno che nello schema sia presente un'annotazione speciale, specifica di WCF. L'annotazione indica che un modello specifico rappresenta un'associazione. Per altre informazioni, vedere Riferimento dello schema di contratto dati.
In genere, un elenco viene importato come un contratto dati della raccolta che deriva da un elenco generico o come una matrice .NET Framework, a seconda che lo schema segua o meno il modello di denominazione standard per le raccolte. Questo argomento è descritto in modo più dettagliato in Tipi di raccolta nei contratti di dati. Le associazioni normalmente vengono importate come un tipo Dictionary<TKey,TValue> o un contratto dati della raccolta che deriva dall'oggetto dizionario. Si consideri ad esempio lo schema seguente.
<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>
Questo schema viene importato come segue (vengono visualizzati i campi anziché le proprietà per migliorare la leggibilità).
[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
È possibile personalizzare i tipi di raccolta generati per tali modelli di schema. Ad esempio, è possibile generare raccolte che derivano da BindingList<T> anziché dalla classe List<T> per associare il tipo a una casella di riepilogo e fare in modo che venga aggiornato automaticamente quando il contenuto della raccolta viene modificato. A questo scopo, impostare la proprietà ReferencedCollectionTypes della classe ImportOptions su un elenco di tipi di raccolta da usare (successivamente indicati come i tipi a cui si fa riferimento). Quando si importa una raccolta, questo elenco di tipi di raccolta a cui si fa riferimento viene analizzato e la raccolta più corrispondente viene usata, se ne viene trovata una. Le associazioni vengono messe in corrispondenza solo con i tipi che implementano l'interfaccia IDictionary generica o non generica, mentre gli elenchi vengono messi in corrispondenza con qualsiasi tipo di raccolta supportato.
Ad esempio, se la proprietà ReferencedCollectionTypes è impostata su una classe BindingList<T>, the people
nell'esempio precedente viene generato come segue.
[CollectionDataContract(ItemName = "person")]
public class people : BindingList<string> { }
<CollectionDataContract(ItemName:="person")> _
Public Class people
Inherits BindingList(Of String)
Un tipo generico chiuso è considerato la corrispondenza migliore, ad esempio, se i tipi BindingList(Of Integer)
e ArrayList vengono passati alla raccolta di tipi a cui si fa riferimento, qualsiasi elenco di numeri interi trovato nello schema viene importato come un tipo BindingList(Of Integer)
. Qualsiasi altro elenco, ad esempio, un List(Of String)
, viene importato come un ArrayList
.
Se viene aggiunto un tipo che implementa l'interfaccia IDictionary
generica alla raccolta di tipi a cui si fa riferimento, i relativi parametri del tipo devono essere completamente aperti o completamente chiusi.
Non è consentito l'uso di duplicati. Ad esempio, non è possibile aggiungere List(Of Integer)
e Collection(Of Integer)
ai tipi a cui si fa riferimento. Questa operazione renderebbe impossibile determinare quale elemento deve essere usato quando nello schema è presente un elenco di numeri interi. I duplicati verranno rilevati solo se nello schema esiste un tipo che espone il problema dei duplicati. Se ad esempio lo schema importato non contiene elenchi di numeri interi, è consentito avere sia List(Of Integer)
che Collection(Of Integer)
nella raccolta di tipi a cui si fa riferimento, ma nessuno dei due elementi eserciterà alcun effetto.
Il meccanismo dei tipi della raccolta a cui si fa riferimento funziona ugualmente per le raccolte di tipi complessi (incluse le raccolte di altre raccolte) e non solo per raccolte di primitivi.
La proprietà ReferencedCollectionTypes
corrisponde all'opzione /collectionType dello strumento SvcUtil.exe. Si noti che per fare riferimento a più tipi di raccolta, l'opzione /collectionType deve essere specificata più volte. Se il tipo non è presente in MsCorLib.dll, è necessario fare riferimento anche all'assembly corrispondente usando l'opzione /reference.
Opzioni di importazione: riferimento ai tipi esistenti
A volte i tipi nello schema corrispondono ai tipi .NET Framework esistenti e non è necessario generare questi tipi da zero (questa sezione si applica solo ai tipi non di raccolta. Per i tipi di raccolta, vedere la sezione precedente).
Ad esempio, è possibile che si disponga di un contratto dati "Person" standard per tutta l'azienda che si desidera usare sempre quando si rappresenta una persona. Ogni volta che un servizio usa questo tipo e lo schema è presente nei metadati del servizio, è possibile riutilizzare il tipo Person
esistente durante l'importazione di questo schema anziché generare un nuovo tipo per ogni servizio.
Per questo scopo, passare un elenco dei tipi .NET Framework che si desidera riutilizzare nella raccolta che la proprietà ReferencedTypes restituisce sulla classe ImportOptions. Se uno di questi tipi presenta un nome del contratto dati e un spazio dei nomi corrispondenti al nome e allo spazio dei nomi di un tipo di schema, viene eseguito un confronto strutturale. Se si stabilisce che i tipi hanno nomi e strutture corrispondenti, il tipo .NET Framework esistente viene riutilizzato anziché generare un nuovo tipo. Se corrisponde solo il nome ma non la struttura, viene generata un'eccezione. Si noti che non è consentito il controllo delle versioni quando si fa riferimento ai tipi (ad esempio, aggiungendo nuovi membri dati facoltativi). Le strutture devono corrispondere perfettamente.
È possibile aggiungere più tipi con lo stesso nome del contratto dati e lo stesso spazio dei nomi alla raccolta di tipi a cui si fa riferimento, fintanto che non vengono importati tipi dello schema con tale nome e spazio dei nomi. In questo modo è possibile aggiungere facilmente tutti i tipi di un assembly alla raccolta senza temere che vengano creati duplicati dei tipi che non sono presenti nello schema.
La proprietà ReferencedTypes
corrisponde all'opzione /reference in determinate modalità di funzionamento dello strumento Svcutil.exe.
Nota
Se si usa Svcutil.exe o (in Visual Studio) gli strumenti Aggiungi riferimento al servizio, viene fatto automaticamente riferimento a tutti i tipi in MsCorLib.dll.
Opzioni di importazione: importazione di schemi diversi dal contratto dati come tipi IXmlSerializable
XsdDataContractImporter supporta un sottoinsieme limitato dello schema. Se sono presenti costrutti dello schema non supportati (ad esempio, attributi XML), il tentativo di importazione non riesce e viene generata un'eccezione. Tuttavia, l'impostazione della proprietà ImportXmlType su true
estende l'intervallo di schemi supportati. Se impostato su true
, XsdDataContractImporter genera tipi che implementano l'interfaccia IXmlSerializable. In questo modo viene consentito l'accesso diretto alla rappresentazione XML di questi tipi.
Considerazioni sulla progettazione
Può risultare difficile lavorare direttamente con la rappresentazione XML con tipizzazione debole, pertanto è consigliabile usare un motore di serializzazione alternativo, ad esempio XmlSerializer, per lavorare con schemi non compatibili con i contratti dati in modo fortemente tipizzato. Per altre informazioni, vedere Uso della classe XmlSerializer.
Alcuni costrutti dello schema non possono essere importati da XsdDataContractImporter anche se la proprietà ImportXmlType è impostata su
true
. Anche in questo caso, è consigliabile usare XmlSerializer.I costrutti di schema esatti supportati sia quando ImportXmlType è
true
che quando èfalse
sono descritti in Riferimento allo schema del contratto di dati.Gli schemi per i tipi IXmlSerializable generati non conservano la fedeltà quando vengono importati ed esportati. In altre parole, se si esporta lo schema dai tipi generati e lo si importa come classi, non si ottiene lo schema originale.
È possibile combinare l'opzione ImportXmlType con l'opzione ReferencedTypes descritta in precedenza. Per i tipi che devono essere generati come implementazioni IXmlSerializable, il controllo strutturale viene ignorato se si usa la funzionalità ReferencedTypes.
L'opzione ImportXmlType corrisponde all'opzione /importXmlTypes dello strumento Svcutil.exe.
Utilizzo dei tipi IXmlSerializable generati
I tipi IXmlSerializable
generati contengono un campo privato denominato "nodesField" che restituisce una matrice di oggetti XmlNode. Quando si deserializza un'istanza di questo tipo, è possibile accedere ai dati XML direttamente tramite questo campo usando il modello DOM XML. Quando si serializza un'istanza di questo tipo, è possibile impostare questo campo sui dati XML desiderati e verrà serializzato.
Questa operazione viene eseguita tramite l'implementazione dell'interfaccia IXmlSerializable
. Nel tipo IXmlSerializable
generato, l'implementazione ReadXml chiama il metodo ReadNodes della classe XmlSerializableServices. Si tratta di un metodo helper che converte il codice XML fornito tramite un XmlReader in una matrice di oggetti XmlNode. L'implementazione WriteXml esegue l'operazione inversa e converte la matrice di oggetti XmlNode
a una sequenza di chiamate a XmlWriter. Questa operazione viene eseguita mediante il metodo WriteNodes.
È possibile eseguire il processo di esportazione dello schema sulle classi IXmlSerializable
generate. Come illustrato in precedenza, non è possibile ottenere lo schema originale. Si otterrà invece il tipo XSD standard "anyType" che è un elemento jolly per qualsiasi tipo XSD.
Questa operazione viene eseguita applicando l'attributo XmlSchemaProviderAttribute alle classi IXmlSerializable
generate e specificando un metodo che chiama il metodo AddDefaultSchema per generare il tipo "anyType".
Nota
Il tipo XmlSerializableServices esiste esclusivamente per supportare questa particolare funzionalità. Non è consigliabile usarlo per qualsiasi altro scopo.
Opzioni di importazione: opzioni avanzate
Di seguito sono elencate le opzioni importazione avanzate:
Proprietà CodeProvider. Specifica la classe CodeDomProvider da usare per generare il codice per le classi generate. Il meccanismo di importazione tenta di evitare funzionalità non supportate da CodeDomProvider. Se la proprietà CodeProvider non è impostata, il set completo di funzionalità .NET Framework viene usato senza restrizioni.
Proprietà DataContractSurrogate. È possibile specificare un'implementazione IDataContractSurrogate con questa proprietà. IDataContractSurrogate personalizza il processo di importazione. Per altre informazioni, vedere Surrogati del contratto dati. Per impostazione predefinita, non viene usato alcun surrogato.
Vedi anche
- DataContractSerializer
- XsdDataContractImporter
- XsdDataContractExporter
- ImportOptions
- Informazioni di riferimento sullo schema del contratto di dati
- Surrogati di contratti di dati
- Importazione ed esportazione di schemi
- Esportazione di schemi da classi
- Informazioni di riferimento sullo schema del contratto di dati