クラスを作成するためのスキーマのインポート

Windows Communication Foundation (WCF) で使用できるクラスをスキーマから作成するには、XsdDataContractImporter クラスを使用します。 ここでは、生成時に指定できる各種のオプションについて解説します。

インポート処理

スキーマのインポート処理は、XmlSchemaSet を用意し、CodeCompileUnit を生成することから始まります。

XmlSchemaSet は、.NET Framework のスキーマ オブジェクト モデル (SOM) の一部であり、一連の XML スキーマ定義言語 (XSD) スキーマ ドキュメントを表します。 一連の XSD ドキュメントから XmlSchemaSet オブジェクトを生成する過程では、各ドキュメントを逆シリアル化して XmlSchema オブジェクトにし (XmlSerializer を使用)、新規に作っておいた空の XmlSchemaSet に追加していきます。

CodeCompileUnit は、.NET Framework のコード ドキュメント オブジェクト モデル (CodeDOM) の一部であり、.NET Framework コードを抽象化して表します。 実際のコードを CodeCompileUnit から生成するには、CodeDomProvider のサブクラスである、CSharpCodeProviderVBCodeProvider を使います。

スキーマをインポートするには

  1. XsdDataContractImporter のインスタンスを作成します。

  2. 任意。 CodeCompileUnit をコンストラクターに渡します。 スキーマのインポート時に生成された型がこの CodeCompileUnit インスタンスに追加されるので、以降の処理は、CodeCompileUnit が空でない状態から始まります。

  3. 任意。 CanImport メソッドのいずれかを呼び出します。 指定されたスキーマが有効なデータ コントラクト スキーマであって、インポートしても問題ないかどうかを判断するメソッドです。 CanImport メソッドには、次の手順に出てくる Import と同様に、オーバーロードがあります。

  4. オーバーロードされた Import メソッドのいずれか、たとえば Import(XmlSchemaSet) を呼び出します。

    これは、指定しなければならない引数が XmlSchemaSet だけという最も簡潔なオーバーロードであり、匿名型を含め、一連のスキーマに記述されている型をすべてインポートできます。 他に、XSD 型、あるいはインポートする型のリストを (XmlQualifiedName、または XmlQualifiedName オブジェクトのコレクションとして) 指定できるオーバーロードもあります。 この場合は、指定した型だけがインポートされます。 また、引数として XmlSchemaElement を受け取り、XmlSchemaSet のうち特定の要素を、対応する型 (匿名型を含む) と併せてインポートするオーバーロードもあります。 このメソッドの戻り値は XmlQualifiedName であり、この要素に対応して生成された型のデータ コントラクト名を表します。

    Import メソッドを何度も呼び出すと、同じ CodeCompileUnit に複数の項目が追加されます。 CodeCompileUnit に既に存在する型は生成されません。 したがって、Import オブジェクトをいくつも使うのではなく、同じ XsdDataContractImporterXsdDataContractImporter を必要な回数呼び出すようにしてください。 同じ型が重複して生成されることがないので、この方法をお勧めします。

    Note

    インポート中に障害が発生した場合の CodeCompileUnit の状態は不定です。 このような CodeCompileUnit を使うと、セキュリティ上の脆弱性につながるおそれがあります。

  5. CodeCompileUnit プロパティを介して、 CodeCompileUnit にアクセスします。

インポート オプション : 生成された型のカスタマイズ

OptionsXsdDataContractImporter プロパティとして ImportOptions クラスのインスタンスを設定することにより、インポート処理の方法を制御できます。 生成される型に直接影響するオプションが多数あります。

アクセス レベルの制御 (GenerateInternal または /internal スイッチ)

これは、ServiceModel メタデータ ユーティリティ ツール (Svcutil.exe)/internal スイッチに相当します。

通常、スキーマから生成されるのはパブリック型です。ここにプライベート フィールドや対応するパブリック データ メンバー プロパティが定義されます。 パブリック型ではなく内部型を生成したい場合は、GenerateInternal プロパティを true としてください。

次の例は、GenerateInternal プロパティが 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

名前空間の制御 (Namespaces または /namespace スイッチ)

これは、Svcutil.exe ツールの /namespace スイッチに相当します。

通常、スキーマから生成された型は .NET Framework 名前空間に生成され、各 XSD 名前空間が、「データ コントラクト スキーマ の参照」で説明されているマッピングに従って、特定の .NET Framework 名前空間に相当します。 この対応関係は、NamespacesDictionary<TKey,TValue> プロパティでカスタマイズできます。 特定の XSD 名前空間がディクショナリに存在する場合は、その XSD 名前空間に対応する .NET Framework 名前空間もディクショナリから取得されます。

たとえば、次のスキーマを考えます。

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

次の例では、Namespaces プロパティを使用して http://schemas.contoso.com/carSchema 名前空間を "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"))

SerializableAttribute の追加 (GenerateSerializable または /serializable スイッチ)

これは、Svcutil.exe ツールの /serializable スイッチに相当します。

スキーマから生成された型を、.NET Framework ランタイムのシリアル化エンジンでシリアル化することが必要な場合があります。 これは、.NET Framework リモート処理に型を使用する場合に便利です。 そのためには、生成された型に対して、通常の SerializableAttribute 属性に加え、DataContractAttribute 属性も適用する必要があります。 GenerateSerializable オプションを true に設定すると、この属性が自動的に生成されます。

Vehicle オプションを GenerateSerializable に設定して 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

データ バインディング機能の追加 (EnableDataBinding または /enableDataBinding スイッチ)

これは、Svcutil.exe ツールの /enableDataBinding スイッチに相当します。

スキーマから生成された型を GUI コンポーネントにバインドして、この型のインスタンスを更新したとき、自動的に UI にも反映されるようにする場合があります。 XsdDataContractImporter は、生成する型に INotifyPropertyChanged インターフェイスを実装して、プロパティが変更されるとイベントが発生するようにすることができます。 このインターフェイスをサポートするクライアント側 UI プログラミング環境 (Windows Presentation Foundation (WPF) など) で使用する型を生成している場合は、EnableDataBinding プロパティの値を true に設定して、この機能を有効にします。

Vehicle プロパティを EnableDataBinding に設定して生成した 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

インポート オプション: コレクション型の選択

XML でアイテムのコレクションを表すパターンには、アイテムのリストと、アイテム間の関連付けの 2 種類があります。 文字列のリストの例を次に示します。

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

文字列と整数 (city namepopulation) を関連付ける例を次に示します。

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

Note

関連付けもリストと見なすことができます。 たとえば、上記の関連付けは、文字列と整数の 2 つのフィールドから成る複雑な city オブジェクトのリストと考えることも可能です。 どちらの方法であっても、XSD スキーマで表現できます。 リストと関連付けを区別する方法はないため、WCF に固有の特別な注釈がスキーマ内にない限り、このようなパターンは常にリストとして扱われます。 注釈がある場合は、関連付けを表すものとして扱われます。 詳細については、「データ コントラクト スキーマの参照」を参照してください。

リストは通常、スキーマがコレクションの標準的な命名パターンに従っているかどうかに応じて、ジェネリック リストから派生したコレクション データ コントラクト、または.NET Framework の配列としてインポートされます。 詳細については、「データ コントラクトのコレクション型」を参照してください。 関連付けは通常、Dictionary<TKey,TValue>、または辞書オブジェクトから派生したコレクション データ コントラクトとしてインポートされます。 たとえば、次のスキーマを考えます。

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

これをインポートすると次のようになります (読みやすいようプロパティの代わりにフィールドを記載)。

[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

このようなスキーマ パターンに対し、生成されるコレクション型をカスタマイズすることも可能です。 たとえば、BindingList<T> クラスではなく List<T> クラスからコレクションを生成することにより、型をリスト ボックスにバインドし、コレクションの内容が変わると自動的にリスト ボックスにも反映されるようにすることを考えてみましょう。 これは、ReferencedCollectionTypes クラスの ImportOptions プロパティとして、実際に使うコレクション型 (以下、"参照される" 型) のリストを設定すると実現できます。 コレクションのインポート時には、参照されるコレクション型のリストが順に調べられ、最も適したコレクションがあれば、それが使用されます。 関連付けは IDictionary インターフェイス (ジェネリックか否かにかかわらず) を実装した型にしか照合されませんが、リストはサポートされているすべてのコレクション型に照合されます。

たとえば ReferencedCollectionTypes プロパティに BindingList<T> を設定すると、上記の例にある people 型は次のように生成されます。

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

クローズ ジェネリック型 (型パラメーターすべてに具体的な型が指定されているジェネリック型) は、合致度が最も高いと見なされます。 したがって、たとえば BindingList(Of Integer)ArrayList の 2 つの型を "参照される" 型のコレクションに渡した場合、スキーマ内に整数のリストがあると、すべて BindingList(Of Integer) としてインポートされます。 一方、それ以外のリスト (List(Of String) など) は、ArrayList としてインポートされます。

ジェネリック IDictionary インターフェイスを実装する型を "参照される" 型のコレクションに追加した場合、型パラメーターは、完全にオープンであるか完全にクローズであるかのどちらかです。

複製は使用できません。 たとえば List(Of Integer)Collection(Of Integer) の両方を "参照される" 型に追加することはできません。 仮に追加したとしても、整数のリストがスキーマ中に表れたとき、どちらを使うか決められないからです。 もっとも、このような重複が検出されるのは、それが問題になるような型がスキーマ中にある場合だけです。 インポートするスキーマ中に、たとえば整数のリストがないのであれば、List(Of Integer)Collection(Of Integer) の両方が "参照される" 型のコレクションにあっても問題はなく、単に何にも使われなかったというだけのことです。

参照されるコレクション型のしくみは、プリミティブ型のコレクションばかりでなく、複合型のコレクション (他のコレクションから成るコレクションを含む) にも同様に働きます。

ReferencedCollectionTypes プロパティは、SvcUtil.exe ツールの /collectionType スイッチに相当します。 複数のコレクション型を参照する場合は、 /collectionType スイッチを繰り返し指定する必要があります。 型が MsCorLib.dll に含まれていない場合は、そのアセンブリを、 /reference スイッチを使用して参照する必要があります。

インポート オプション : 既存の型の参照

スキーマ内の型が既存の .NET Framework 型に対応する場合は、その型をゼロから作成する必要はありません (以下の説明は、非コレクション型にのみ当てはまります。コレクション型については、前のセクションを参照してください)。

たとえば、企業全体で共通に、"Person" というデータ コントラクト型を使っており、人を表す場合には常にこれを使いたいとします。 いくつかのサービスでこの型を利用し、そのスキーマがサービス メタデータに表示される場合は、このスキーマをインポートするときに既存の Person 型を再利用でき、サービスごとに新たに生成する必要がありません。

これを行うには、再利用する .NET Framework 型のリストを、ImportOptions クラスで ReferencedTypes プロパティから返されるコレクション内に渡します。 これらの型のいずれかが、スキーマ型の名前および名前空間と一致すれば、その構造が比較されます。 名前と構造が両方とも一致すると判断された場合は、型が新たに生成されず、既存の .NET Framework 型が再利用されます。 名前だけが一致して構造が一致しない場合は、例外が発生します。 型の参照時に、バージョンによる (オプションのデータ メンバーの追加などの) 差異は許容されません。 構造は厳密に合致しなければなりません。

"参照される" 型のコレクションに、データ コントラクト名と名前空間が同一である型を重複して追加しても、その名前と名前空間でスキーマ型をインポートしない限り問題ありません。 したがって、実際にはスキーマに現れることのない型については重複を気にすることなく、アセンブリ内の型をすべてコレクションに追加してしまうことができます。

ReferencedTypes プロパティは、Svcutil.exe ツールの特定の実行モードの /reference スイッチに相当します。

Note

Svcutil.exe ツール、または (Visual Studio の) "サービス参照の追加" ツールの使用時には、MsCorLib.dll のすべての型が自動的に参照されます。

インポート オプション : 非データ コントラクト スキーマを IXmlSerializable 型としてインポート

XsdDataContractImporter は、どのようなスキーマでもインポートできるわけではありません。 未対応のスキーマ構造 (たとえば XML 属性) をインポートしようとすると例外が発生します。 ただし ImportXmlType プロパティを true に設定すると、対応可能なスキーマの範囲が広がります。 true に設定すると、XsdDataContractImporter は、IXmlSerializable インターフェイスを実装した型を生成します。 そのため、これらの型の XML 表現に直接アクセスできるようになります。

デザインに関する考慮事項
  • 弱く型指定された XML 表現を直接扱うのは困難です。 データ コントラクトと互換性がないスキーマを厳密に型指定された方法で操作するには、XmlSerializer などの別のシリアル化エンジンの使用を検討します。 詳細については、「XmlSerializer クラスの使用」を参照してください。

  • スキーマ構造によっては、XsdDataContractImporter プロパティを ImportXmlType に設定しても、true でインポートできない場合があります。 このような場合も、XmlSerializer の使用を検討します。

  • ImportXmlTypetrue または false である場合にサポートされる正確なスキーマ構造は、「データ コントラクト スキーマの参照」で説明されています。

  • 生成された IXmlSerializable 型に対するスキーマには、いったんインポートしてからエクスポートした場合、忠実性が維持されません。 つまり、生成された型を基にスキーマをエクスポートし、再びこれをクラスとしてインポートした場合、元どおりのスキーマにはなりません。

ImportXmlType のオプションは、上記の ReferencedTypes オプションと組み合わせて指定できます。 IXmlSerializable を実装する形で生成される型に関しては、ReferencedTypes で型を指定する際、構造がチェックされません。

ImportXmlType オプションは、Svcutil.exe ツールの /importXmlTypes スイッチに相当します。

生成された IXmlSerializable 型の使い方

生成された IXmlSerializable 型には、XmlNode オブジェクトの配列を返す "nodesField" というプライベート フィールドがあります。 このような型のインスタンスを逆シリアル化すると、XML ドキュメント オブジェクト モデルに基づき、このフィールドを介して XML データに直接アクセスできるようになります。 この型のインスタンスをシリアル化する際、"nodesField" フィールドに XML データを設定しておくと、これもシリアル化の対象になります。

以上の処理は IXmlSerializable インターフェイスで実装されます。 生成された IXmlSerializable 型では、ReadXml の実装により、ReadNodes クラスの XmlSerializableServices メソッドが呼び出されます。 これは XmlReader を介して渡された XML を XmlNode オブジェクトに変換するヘルパー メソッドです。 WriteXml を実装したコードは逆に、XmlNode オブジェクトの配列を、一連の XmlWriter の呼び出しに変換します。 それには WriteNodes メソッドを使います。

スキーマのエクスポート処理は、生成された IXmlSerializable クラスに対して実行できます。 ただし、先に述べたように、元どおりのスキーマは再現されません。 代わりに、任意の XSD 型を表す、"anyType" という標準型になります。

これを実現するには、生成された IXmlSerializable クラスに XmlSchemaProviderAttribute 属性を適用し、AddDefaultSchema メソッドを呼び出すメソッドを指定して "anyType" 型を生成します。

Note

XmlSerializableServices 型は、この機能を提供するためだけに用意されています。 他の目的で使用することはお勧めできません。

インポート オプション : 高度なオプション

他にも、次のようなオプションがあります。

関連項目