Importazione dello schema per generare classi
Per generare classi da schemi che possono essere utilizzati con Windows Communication Foundation (WCF), utilizzare 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, utilizzare 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 di CodeCompileUnit anziché iniziare con un'istanza CodeCompileUnit 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 di Import (il passaggio successivo).
Chiamare uno dei metodi Import di overload, ad esempio il metodo Import.
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 oggetti XmlQualifiedName). In questo caso, vengono importati solo i tipi specificati. Un overload accetta una classe XmlSchemaElement che importa un elemento specifico fuori dalla classe XmlSchemaSet, insieme al tipo associato (indipendentemente dal fatto che sia anonimo o meno). Questo overload restituisce una classe XmlQualifiedName 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 classe CodeCompileUnit. Se esiste già un tipo in CodeCompileUnit, non ne viene generato un altro. È consigliabile chiamare più volte Import sullo stesso XsdDataContractImporter anziché utilizzare più oggetti XsdDataContractImporter. 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 utilizza una classe CodeCompileUnit 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 di 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.
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;}
}
}
Controllo degli spazi dei nomi (opzione Namespaces o /namespace)
Corrisponde all'opzione /namespace nello strumento Svcutil.exe.
In genere, i tipi generati dallo schema vengono generati negli spazi dei nomi .NET Framework e ogni spazio dei nomi XSD corrisponde a uno specifico spazio dei nomi .NET Framework in base a un mapping descritto in Riferimento allo schema del contratto dati. È possibile personalizzare questo mapping impostando la proprietà Namespaces su Dictionary. 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 utilizzata la proprietà Namespaces per associare lo spazio dei nomi "http://schemas.contoso.com/carSchema" a "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.");
}
}
}
Aggiunta di SerializableAttribute (opzione GenerateSerializable o /serializable)
Corrisponde all'opzione /serializable nello strumento Svcutil.exe.
In alcuni casi può essere importante che i tipi generati dallo schema possano essere utilizzati con i motori di serializzazione di runtime di .NET Framework (ad esempio, le classi BinaryFormatter e SoapFormatter). Questo aspetto è utile quando si utilizzano 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()> _
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.");
}
}
}
Aggiunta del supporto delle associazioni dati (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 utilizzare 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.
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;}
}
}
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 ulteriori informazioni, vedere Riferimento allo schema del 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 aspetto viene descritto in maggiore dettaglio in Tipi di raccolta nei contratti dati. Le associazioni normalmente vengono importate come un tipo Dictionary 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à).
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> {}
È possibile personalizzare i tipi di raccolta generati per tali modelli di schema. Ad esempio, è possibile generare raccolte che derivano da BindingList anziché dalla classe List 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 utilizzare (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 utilizzata, 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, the people
nell'esempio precedente viene generato come segue.
<CollectionDataContract(ItemName := "person")> _
Public Class people
Inherits BindingList(Of String)
[CollectionDataContract(ItemName = "person")]
public class people : BindingList<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'utilizzo 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 utilizzato 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 al relativo assembly utilizzando 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 utilizzare sempre quando si rappresenta una persona. Ogni volta che un servizio utilizza 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 alcune modalità di utilizzo dello strumento Svcutil.exe.
Nota: |
---|
Se si utilizza 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 di progettazione
Può risultare difficile lavorare direttamente con la rappresentazione XML con tipizzazione debole, pertanto è consigliabile utilizzare un motore di serializzazione alternativo, ad esempio XmlSerializer, per lavorare con schemi non compatibili con i contratti dati in modo fortemente tipizzato. Per ulteriori informazioni, vedere Utilizzo 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 utilizzare XmlSerializer.
I costrutti dello schema supportati sia quando ImportXmlType è true che quando è false sono descritti in Riferimento allo schema del contratto 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 utilizza 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 utilizzando 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 utilizzarlo per qualsiasi altro scopo. |
Opzioni di importazione: opzioni avanzate
Di seguito sono elencate le opzioni importazione avanzate:
Proprietà CodeProvider. Specifica la classe CodeDomProvider da utilizzare per generare il codice per le classi generate. Il meccanismo di importazione tenta di evitare funzionalità non supportate da CodeDomProvider. Ad esempio, il linguaggio di J# non supporta generics. Se si specifica il provider di codice J# in questa proprietà, nessuno tipo generico viene generato nella classe CodeCompileUnit dell'unità di importazione. Se la proprietà CodeProvider non è impostata, il set completo di funzionalità .NET Framework viene utilizzato senza restrizioni.
Proprietà DataContractSurrogate. È possibile specificare un'implementazione IDataContractSurrogate con questa proprietà. IDataContractSurrogate personalizza il processo di importazione. Per ulteriori informazioni, vedere Surrogati del contratto dati. Per impostazione predefinita, non viene utilizzato alcun surrogato.
Vedere anche
Riferimento
DataContractSerializer
XsdDataContractImporter
XsdDataContractExporter
ImportOptions
Concetti
Riferimento allo schema del contratto dati
Surrogati del contratto dati
Importazione ed esportazione dello schema
Esportazione di schemi dalle classi
Riferimento allo schema del contratto dati