Importowanie schematu w celu generowania klas

Aby wygenerować klasy na podstawie schematów, których można używać w programie Windows Communication Foundation (WCF), użyj XsdDataContractImporter klasy . W tym temacie opisano proces i odmiany.

Proces importowania

Proces importowania schematu rozpoczyna się od elementu XmlSchemaSet i tworzy element CodeCompileUnit.

Element XmlSchemaSet jest częścią modelu obiektów schematu programu .NET Framework (SOM), która reprezentuje zestaw dokumentów schematu języka XML (XSD). Aby utworzyć XmlSchemaSet obiekt na podstawie zestawu dokumentów XSD, deserializuj każdy dokument do XmlSchema obiektu (przy użyciu XmlSerializerelementu ) i dodaj te obiekty do nowego XmlSchemaSetobiektu .

Element CodeCompileUnit jest częścią modelu document object model (CodeDOM) programu .NET Framework, który reprezentuje kod .NET Framework w sposób abstrakcyjny. Aby wygenerować rzeczywisty kod z klasy CodeCompileUnit, użyj podklasy CodeDomProvider klasy , takiej jak CSharpCodeProvider klasa lub VBCodeProvider .

Aby zaimportować schemat

  1. Utwórz wystąpienie elementu XsdDataContractImporter.

  2. Opcjonalny. Przekaż element CodeCompileUnit w konstruktorze. Typy generowane podczas importowania schematu są dodawane do tego CodeCompileUnit wystąpienia zamiast rozpoczynać się od pustego CodeCompileUnitelementu .

  3. Opcjonalny. Wywołaj CanImport jedną z metod. Metoda określa, czy dany schemat jest prawidłowym schematem kontraktu danych i można go zaimportować. Metoda CanImport ma te same przeciążenia co Import (następny krok).

  4. Wywołaj jedną z przeciążonych Import metod, na przykład metodę Import(XmlSchemaSet) .

    Najprostsze przeciążenie przyjmuje element i importuje XmlSchemaSet wszystkie typy, w tym typy anonimowe, znalezione w tym zestawie schematów. Inne przeciążenia umożliwiają określenie typu XSD lub listy typów do zaimportowania (w postaci XmlQualifiedName lub kolekcji XmlQualifiedName obiektów). W takim przypadku importowane są tylko określone typy. Przeciążenie pobiera element XmlSchemaElement , który importuje określony element z XmlSchemaSetklasy , a także skojarzony typ (niezależnie od tego, czy jest anonimowy, czy nie). To przeciążenie zwraca XmlQualifiedNamewartość , która reprezentuje nazwę kontraktu danych typu wygenerowanego dla tego elementu.

    Wiele wywołań Import metody powoduje dodanie wielu elementów do tego samego CodeCompileUnitelementu . Typ nie jest generowany do elementu CodeCompileUnit , jeśli już istnieje. Wywołaj Import wiele razy w tym samym XsdDataContractImporter zamiast używać wielu XsdDataContractImporter obiektów. Jest to zalecany sposób unikania generowania zduplikowanych typów.

    Uwaga

    Jeśli podczas importowania wystąpi błąd, CodeCompileUnit element będzie w stanie nieprzewidywalnym. Użycie elementu CodeCompileUnit wynikającego z nieudanego importu może spowodować uwidocznienie luk w zabezpieczeniach.

  5. CodeCompileUnit Uzyskaj dostęp do właściwości za pośrednictwem CodeCompileUnit właściwości .

Opcje importu: dostosowywanie wygenerowanych typów

Właściwość klasy XsdDataContractImporter można ustawić Options na wystąpienie ImportOptions klasy, aby kontrolować różne aspekty procesu importowania. Wiele opcji ma bezpośredni wpływ na generowane typy.

Kontrolowanie poziomu dostępu (GenerateInternal lub /internal switch)

Odpowiada to /internal switch w narzędziu ServiceModel Metadata Tool (Svcutil.exe).

Zwykle typy publiczne są generowane na podstawie schematu z polami prywatnymi i pasującymi właściwościami elementów członkowskich danych publicznych. Aby zamiast tego wygenerować typy wewnętrzne, ustaw GenerateInternal właściwość na truewartość .

Poniższy przykład przedstawia schemat przekształcony w klasę wewnętrzną, gdy właściwość jest ustawiona GenerateInternal na 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

Kontrolowanie przestrzeni nazw (przestrzenie nazw lub przełącznik /namespace)

Odpowiada to przełącznikowi /namespace na narzędziu Svcutil.exe .

Zwykle typy generowane na podstawie schematu są generowane w przestrzeniach nazw programu .NET Framework, z każdą przestrzenią nazw XSD odpowiadającą określonej przestrzeni nazw programu .NET Framework zgodnie z mapowaniem opisanym w temacie Dokumentacja schematu kontraktu danych. To mapowanie można dostosować według Namespaces właściwości do elementu Dictionary<TKey,TValue>. Jeśli dana przestrzeń nazw XSD zostanie znaleziona w słowniku, zgodna przestrzeń nazw programu .NET Framework również zostanie pobrana ze słownika.

Rozważmy na przykład następujący schemat.

<xs:schema targetNamespace="http://schemas.contoso.com/carSchema">
  <xs:complexType name="Vehicle">
    <!-- details omitted... -->
  </xs:complexType>
</xs:schema>

W poniższym przykładzie użyto Namespaces właściwości do mapowania http://schemas.contoso.com/carSchema przestrzeni nazw na "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"))

Dodawanie elementu SerializableAttribute (GenerateSerializable lub /serializable switch)

Odpowiada to /serializable przełącznik na narzędziu Svcutil.exe .

Czasami ważne jest, aby typy generowane na podstawie schematu można było używać z aparatami serializacji środowiska uruchomieniowego programu .NET Framework. Jest to przydatne w przypadku używania typów na potrzeby komunikacji wirtualnej programu .NET Framework. Aby to włączyć, należy zastosować SerializableAttribute atrybut do wygenerowanych typów oprócz zwykłego DataContractAttribute atrybutu. Atrybut jest generowany automatycznie, jeśli GenerateSerializable opcja importu jest ustawiona na true.

W poniższym przykładzie pokazano klasę Vehicle wygenerowaną z opcją importu ustawioną GenerateSerializable na wartość 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

Dodawanie obsługi powiązań danych (EnableDataBinding lub przełącznik /enableDataBinding)

Odpowiada to przełącznikowi /enableDataBinding w narzędziu Svcutil.exe.

Czasami można powiązać typy wygenerowane ze schematu z graficznymi składnikami interfejsu użytkownika, aby każda aktualizacja wystąpień tych typów automatycznie zaktualizowała interfejs użytkownika. Element XsdDataContractImporter może generować typy, które implementują INotifyPropertyChanged interfejs w taki sposób, że każda zmiana właściwości wyzwala zdarzenie. Jeśli generujesz typy do użycia ze środowiskiem programowania interfejsu użytkownika klienta, które obsługuje ten interfejs (np. Windows Presentation Foundation (WPF), ustaw EnableDataBinding właściwość na wartość , aby true włączyć tę funkcję.

W poniższym przykładzie pokazano klasę Vehicle wygenerowaną za pomocą EnableDataBinding zestawu na wartość 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

Opcje importu: wybieranie typów kolekcji

Dwa specjalne wzorce w kodzie XML reprezentują kolekcje elementów: listy elementów i skojarzeń między jednym elementem a drugim. Poniżej przedstawiono przykład listy ciągów.

<People>
  <person>Alice</person>
  <person>Bob</person>
  <person>Charlie</person>
</People>

Poniżej przedstawiono przykład skojarzenia między ciągiem a liczbą całkowitą (city name i 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>

Uwaga

Każde skojarzenie można również traktować jako listę. Na przykład można wyświetlić powyższe skojarzenie jako listę złożonych city obiektów, które mają dwa pola (pole ciągu i pole liczby całkowitej). Oba wzorce mają reprezentację w schemacie XSD. Nie ma możliwości rozróżnienia między listą a skojarzeniem, więc takie wzorce są zawsze traktowane jako listy, chyba że w schemacie znajduje się specjalna adnotacja specyficzna dla programu WCF. Adnotacja wskazuje, że dany wzorzec reprezentuje skojarzenie. Aby uzyskać więcej informacji, zobacz Dokumentacja schematu kontraktu danych.

Zwykle lista jest importowana jako kontrakt danych kolekcji, który pochodzi z listy ogólnej lub jako tablicy .NET Framework, w zależności od tego, czy schemat jest zgodny ze standardowym wzorcem nazewnictwa kolekcji. Opisano to bardziej szczegółowo w temacie Typy kolekcji w kontraktach danych. Skojarzenia są zwykle importowane jako Dictionary<TKey,TValue> kontrakt danych kolekcji lub, który pochodzi z obiektu słownika. Rozważmy na przykład następujący schemat.

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

Zostanie to zaimportowane w następujący sposób (pola są wyświetlane zamiast właściwości na potrzeby czytelności).

[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

Istnieje możliwość dostosowania typów kolekcji generowanych dla takich wzorców schematu. Na przykład możesz wygenerować kolekcje pochodzące z BindingList<T> klasy zamiast List<T> klasy, aby powiązać typ z polem listy i automatycznie aktualizować zawartość kolekcji. W tym celu ustaw ReferencedCollectionTypes właściwość ImportOptions klasy na listę typów kolekcji, które mają być używane (nazywane dalej typami referencyjnymi). Podczas importowania dowolnej kolekcji ta lista przywoływane typy kolekcji jest skanowana, a najlepiej pasująca kolekcja jest używana, jeśli zostanie znaleziona. Skojarzenia są dopasowywane tylko do typów, które implementują interfejs ogólny lub niegenericzny IDictionary , podczas gdy listy są dopasowywane do dowolnego obsługiwanego typu kolekcji.

Jeśli na przykład ReferencedCollectionTypes właściwość jest ustawiona na BindingList<T>wartość , people typ w poprzednim przykładzie jest generowany w następujący sposób.

[CollectionDataContract(ItemName = "person")]
public class people : BindingList<string> { }
<CollectionDataContract(ItemName:="person")> _
Public Class people
    Inherits BindingList(Of String)

Zamknięty rodzaj jest uważany za najlepsze dopasowanie. Jeśli na przykład typy BindingList(Of Integer) i ArrayList są przekazywane do kolekcji przywoływanego typu, wszystkie listy liczb całkowitych znalezionych w schemacie są importowane jako BindingList(Of Integer). Wszystkie inne listy, na przykład , List(Of String)są importowane jako ArrayList.

Jeśli typ implementujący interfejs ogólny IDictionary jest dodawany do kolekcji przywoływanych typów, jego parametry typu muszą być w pełni otwarte lub w pełni zamknięte.

Duplikaty nie są dozwolone. Na przykład nie można dodać elementu i List(Of Integer) do Collection(Of Integer) przywołynych typów. Uniemożliwiłoby to określenie, które należy użyć w przypadku znalezienia listy liczb całkowitych w schemacie. Duplikaty zostaną wykryte tylko wtedy, gdy istnieje typ schematu, który uwidacznia problem z duplikatami. Jeśli na przykład zaimportowany schemat nie zawiera list liczb całkowitych, może mieć zarówno List(Of Integer) właściwość , jak Collection(Of Integer) i w kolekcji typów, do których się odwołuje, ale żadna z nich nie będzie miała żadnego wpływu.

Mechanizm przywoływania typów kolekcji działa równie dobrze w przypadku kolekcji typów złożonych (w tym kolekcji innych kolekcji), a nie tylko dla kolekcji elementów pierwotnych.

Właściwość ReferencedCollectionTypes odpowiada przełącznikowi /collectionType w narzędziu SvcUtil.exe. Należy pamiętać, że aby odwoływać się do wielu typów kolekcji, przełącznik /collectionType musi być określony wiele razy. Jeśli typ nie znajduje się w MsCorLib.dll, należy również odwołać się do jego zestawu przy użyciu przełącznika /reference .

Opcje importu: odwoływanie się do istniejących typów

Czasami typy w schemacie odpowiadają istniejącym typom programu .NET Framework i nie ma potrzeby generowania tych typów od podstaw. (Ta sekcja dotyczy tylko typów niekolekcji. W przypadku typów kolekcji zobacz poprzednią sekcję).

Na przykład może istnieć standardowy typ kontraktu danych "Osoba" w całej firmie, który zawsze ma być używany podczas reprezentowania osoby. Za każdym razem, gdy niektóre usługi używają tego typu, a jego schemat pojawia się w metadanych usługi, możesz chcieć ponownie użyć istniejącego Person typu podczas importowania tego schematu zamiast generowania nowego dla każdej usługi.

W tym celu przekaż listę typów programu .NET Framework, które mają zostać użyte ponownie w kolekcji ReferencedTypes , którą właściwość zwraca w ImportOptions klasie. Jeśli którykolwiek z tych typów ma nazwę kontraktu danych i przestrzeń nazw zgodną z nazwą i przestrzenią nazw typu schematu, wykonywane jest porównanie strukturalne. Jeśli okaże się, że typy mają zarówno pasujące nazwy, jak i pasujące struktury, istniejący typ programu .NET Framework zostanie ponownie użyty zamiast wygenerować nowy. Jeśli tylko nazwa jest zgodna, ale nie struktura, zgłaszany jest wyjątek. Należy pamiętać, że w przypadku odwoływania się do typów (na przykład dodawania nowych opcjonalnych elementów członkowskich danych nie ma limitu wersji). Struktury muszą być dokładnie zgodne.

Istnieje prawo do dodawania wielu typów o tej samej nazwie kontraktu danych i przestrzeni nazw do kolekcji typów, o ile żadne typy schematów nie są importowane z tą nazwą i przestrzenią nazw. Dzięki temu można łatwo dodać wszystkie typy w zestawie do kolekcji bez obaw o duplikaty typów, które w rzeczywistości nie występują w schemacie.

Właściwość ReferencedTypes odpowiada przełącznikowi /reference w niektórych trybach działania narzędzia Svcutil.exe.

Uwaga

W przypadku korzystania z Svcutil.exe lub (w programie Visual Studio) narzędzia Dodawania odwołań do usługi wszystkie typy w MsCorLib.dll są automatycznie przywołyne.

Opcje importu: importowanie schematu innego niż DataContract jako typy IXmlSerializable

Element XsdDataContractImporter obsługuje ograniczony podzestaw schematu. Jeśli nieobsługiwane konstrukcje schematu są obecne (na przykład atrybuty XML), próba importowania nie powiedzie się z wyjątkiem. Jednak ustawienie ImportXmlType właściwości w celu true rozszerzenia zakresu obsługiwanych schematów. Gdy jest ustawiona XsdDataContractImporter wartość true, program generuje typy, które implementują IXmlSerializable interfejs. Umożliwia to bezpośredni dostęp do reprezentacji XML tych typów.

Zagadnienia dotyczące projektowania
  • Praca ze słabo typizowanej reprezentacji XML może być trudna. Rozważ użycie alternatywnego aparatu serializacji, takiego jak XmlSerializer, do pracy ze schematem niezgodnym z kontraktami danych w sposób silnie typizowane. Aby uzyskać więcej informacji, zobacz Using the XmlSerializer Class (Używanie klasy XmlSerializer).

  • Niektórych konstrukcji schematu nie można zaimportować nawet XsdDataContractImporter wtedy, gdy właściwość jest ustawiona ImportXmlType na true. Ponownie rozważ użycie elementu XmlSerializer w takich przypadkach.

  • Dokładne konstrukcje schematu, które są obsługiwane zarówno wtedy, gdy ImportXmlType jest, jak i truefalse są opisane w dokumentacji schematu kontraktu danych.

  • Schemat dla wygenerowanych IXmlSerializable typów nie zachowuje wierności podczas importowania i eksportowania. Oznacza to, że eksportowanie schematu z wygenerowanych typów i importowanie jako klasy nie zwraca oryginalnego schematu.

Istnieje możliwość połączenia ImportXmlType opcji z wcześniej opisaną opcją ReferencedTypes . W przypadku typów, które muszą być generowane jako IXmlSerializable implementacje, sprawdzanie strukturalne jest pomijane podczas korzystania z ReferencedTypes funkcji.

Opcja ImportXmlType odpowiada przełącznikowi /importXmlTypes w narzędziu Svcutil.exe.

Praca z wygenerowanymi typami IXmlSerializable

Wygenerowane IXmlSerializable typy zawierają pole prywatne o nazwie "nodesField", które zwraca tablicę XmlNode obiektów. Podczas deserializacji wystąpienia takiego typu można uzyskać dostęp do danych XML bezpośrednio za pomocą tego pola przy użyciu modelu obiektów dokumentów XML. Podczas serializacji wystąpienia tego typu można ustawić to pole na żądane dane XML i będzie serializowane.

Jest to realizowane za pośrednictwem implementacji IXmlSerializable . W wygenerowany IXmlSerializable typ implementacja ReadXml wywołuje metodę ReadNodesXmlSerializableServices klasy. Metoda jest metodą pomocnika, która konwertuje kod XML udostępniany za pomocą XmlReader elementu na tablicę XmlNode obiektów. Implementacja WriteXml wykonuje odwrotne czynności i konwertuje tablicę XmlNode obiektów na sekwencję XmlWriter wywołań. Jest to realizowane przy użyciu WriteNodes metody .

Istnieje możliwość uruchomienia procesu eksportu schematu w wygenerowanych IXmlSerializable klasach. Jak wspomniano wcześniej, nie otrzymasz oryginalnego schematu z powrotem. Zamiast tego otrzymasz standardowy typ XSD typu "anyType", który jest symbolem wieloznacznymi dla dowolnego typu XSD.

Jest to realizowane przez zastosowanie atrybutu XmlSchemaProviderAttribute do wygenerowanych IXmlSerializable klas i określenie metody, która wywołuje AddDefaultSchema metodę w celu wygenerowania typu "anyType".

Uwaga

Typ XmlSerializableServices istnieje wyłącznie do obsługi tej konkretnej funkcji. Nie zaleca się stosowania w żadnym innym celu.

Opcje importu: Opcje zaawansowane

Poniżej przedstawiono zaawansowane opcje importowania:

Zobacz też