Udostępnij za pośrednictwem


Importowanie schematu do 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ć obiekt XmlSchemaSet na podstawie zestawu dokumentów XSD, zdeserializuj każdy dokument do obiektu XmlSchema (przy użyciu elementu XmlSerializer) i dodaj te obiekty do nowego obiektu XmlSchemaSet.

Element CodeCompileUnit jest częścią Modelu Obiektów Dokumentów Kodu (CodeDOM) .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 klasy XsdDataContractImporter.

  2. Opcjonalny. Przekaż parametr CodeCompileUnit w konstruktorze. Typy generowane podczas importowania schematu są dodawane do tego CodeCompileUnit wystąpienia zamiast zaczynać od pustego elementu CodeCompileUnit.

  3. Opcjonalny. Wywołaj jedną z metod CanImport. 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 metody przyjmuje XmlSchemaSet jako argument i importuje wszystkie typy, w tym typy anonimowe, znalezione w danym 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 XmlQualifiedName, która reprezentuje nazwę kontraktu danych dla typu wygenerowanego dla tego elementu.

    Wielokrotne wywołania metody Import sprawiają, że do tego samego CodeCompileUnit dodawane są liczne elementy. Typ nie jest generowany do CodeCompileUnit, jeśli już tam istnieje. Wywołaj Import wiele razy na tym samym XsdDataContractImporter zamiast używać wielu obiektów XsdDataContractImporter. Jest to zalecany sposób unikania generowania zduplikowanych typów.

    Uwaga / Notatka

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

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

Opcje importu: dostosowywanie wygenerowanych typów

Możesz ustawić właściwość Options obiektu XsdDataContractImporter na egzemplarz klasy ImportOptions, aby kontrolować różne aspekty procesu importowania. Wiele opcji ma bezpośredni wpływ na generowane typy.

Kontrolowanie poziomu dostępu (GenerateInternal lub przełącznik /internal)

Odpowiada to przełącznikowi /internal na narzędziu ServiceModel Metadata Tool (Svcutil.exe).

Zwykle typy publiczne są generowane na podstawie schematu, zawierając pola prywatne i odpowiadające właściwości członków danych publicznych. Aby zamiast tego wygenerować typy wewnętrzne, ustaw GenerateInternal właściwość na true wartość.

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 .NET Framework, z każdą przestrzenią nazw XSD odpowiadającą określonej przestrzeni nazw .NET Framework zgodnie z mapowaniem opisanym w Dokumentacja schematu kontraktu danych. Można dostosować to mapowanie za pomocą właściwości Namespaces 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 wykorzystano Namespaces właściwość 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 atrybutu SerializableAttribute (GenerateSerializable lub przełącznik /serializable)

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

Czasami ważne jest, aby typy generowane na podstawie schematu można było używać z mechanizmami serializacji środowiska uruchomieniowego .NET Framework. Jest to przydatne w przypadku używania typów na potrzeby zdalnej komunikacji 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 właściwość EnableDataBinding na true, aby włączyć tę funkcję.

W poniższym przykładzie pokazano klasę Vehicle wygenerowaną z EnableDataBinding ustawionego na 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 / Notatka

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> lub jako kontrakt danych kolekcji wywodzący się 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 właściwość ReferencedCollectionTypes klasy ImportOptions na listę typów kolekcji, które mają być używane, dalej nazywanych typami referencyjnymi. Podczas importowania dowolnej kolekcji, ta lista typów kolekcji referencyjnych jest skanowana i jeśli zostanie znaleziona najlepiej pasująca, jest ona używana. 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 właściwość ReferencedCollectionTypes jest ustawiona na BindingList<T>, typ people w przytoczonym 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 typ ogólny 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ć zarówno List(Of Integer), jak i Collection(Of Integer) do przywołanych 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. Na przykład, jeśli zaimportowany schemat nie zawiera list liczb całkowitych, możliwe jest posiadanie zarówno List(Of Integer), jak i Collection(Of Integer) w kolekcji typów, do których się odwołuje, ale żadna z nich nie będzie miała żadnego wpływu.

Mechanizm dotyczący 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 typów prymitywnych.

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 .NET Framework, które chcesz ponownie użyć, do kolekcji, którą zwraca właściwość w klasie ReferencedTypes. 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 przy odwoływaniu się do typów (na przykład przy dodawaniu nowych opcjonalnych elementów danych) nie ma wsparcia dla wersjonowania. 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 / Notatka

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łysowane.

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 właściwości ImportXmlType na true rozszerza zakres obsługiwanych schematów. Gdy jest ustawiona wartość true, XsdDataContractImporter generuje typy, które implementują interfejs IXmlSerializable. Umożliwia to bezpośredni dostęp do reprezentacji XML tych typów.

Zagadnienia dotyczące projektowania
  • Może być trudno pracować bezpośrednio z słabo typizowaną reprezentacją XML. Rozważ użycie alternatywnego mechanizmu serializacji, takiego jak XmlSerializer, do pracy ze schematem niezgodnym z kontraktami danych w sposób silnie typizowany. Aby uzyskać więcej informacji, zobacz Using the XmlSerializer Class (Używanie klasy XmlSerializer).

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

  • Dokładne elementy schematu, które są obsługiwane zarówno dla ImportXmlType jak i true, są opisane w false.

  • Schematy dla wygenerowanych IXmlSerializable typów nie zachowują wierności przy importowaniu i eksportowaniu. 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 implementacje IXmlSerializable, kontrola strukturalna jest pomijana podczas korzystania z funkcji ReferencedTypes.

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

Praca z typami IXmlSerializable generowanymi automatycznie

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 zostanie zserializowane.

Jest to realizowane za pośrednictwem implementacji IXmlSerializable . W wygenerowanym typie IXmlSerializable implementacja ReadXml wywołuje metodę ReadNodes klasy XmlSerializableServices. Metoda jest metodą pomocniczą, która konwertuje XML dostarczany za pomocą XmlReader na tablicę obiektów XmlNode. 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 / Notatka

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 także