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

  1. Creare un'istanza di XsdDataContractImporter.

  2. 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.

  3. 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).

  4. 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 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é usare 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 usa una classe CodeCompileUnit da un'importazione non riuscita, è possibile esporre il sistema a vulnerabilità della protezione.

  5. 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:

Vedi anche