データ コントラクト サロゲート

データ コントラクト サロゲートは、データ コントラクト モデルを基盤として構築された高度な機能です。 この機能は、型をシリアル化または逆シリアル化する方法や、型をメタデータに投影する方法をユーザーが変更する場合に、型のカスタマイズと置換に使用することを目的としています。 サロゲートを使用できるのは、型のデータ コントラクトが指定されていない場合、フィールドやプロパティが DataMemberAttribute 属性でマークされていない場合、またはユーザーがスキーマのバリエーションを動的に作成することを希望している場合などです。

DataContractSerializer を使用して .NET Framework から XML などの適切な形式に変換するときに、データ コントラクト サロゲートを使用してシリアル化と逆シリアル化を行います。 また、XML スキーマ ドキュメント (XSD) などのメタデータ表現を生成するときに、データ コントラクト サロゲートを使用して、型のエクスポートするメタデータを変更できます。 インポート時には、メタデータからコードが作成されますが、この場合にサロゲートを使用すると、生成されるコードもカスタマイズできます。

サロゲートのしくみ

サロゲートは、ある型 ("元の" 型) を別の型 ("サロゲートされた" 型) に割り当てることによって機能します。 元の型 Inventory と新しいサロゲート型 InventorySurrogated の例を以下に示します。 Inventory 型はシリアル化できませんが、InventorySurrogated 型は以下のようになります。

public class Inventory
{
    public int pencils;
    public int pens;
    public int paper;
}

このクラスにはデータ コントラクトが定義されていないため、このクラスをデータ コントラクトを持つサロゲート クラスに変換します。 サロゲートされたクラスの例を次に示します。

[DataContract(Name = "Inventory")]
public class InventorySurrogated
{
    [DataMember]
    public int numpencils;
    [DataMember]
    public int numpaper;
    [DataMember]
    private int numpens;

    public int pens
    {
        get { return numpens; }
        set { numpens = value; }
    }
}

IDataContractSurrogate の実装

データ コントラクト サロゲートを使用するには、IDataContractSurrogate インターフェイスを実装します。

考えられる実装での IDataContractSurrogate の各メソッドの概要を以下に示します。

GetDataContractType

GetDataContractType メソッドは、ある型を別の型に割り当てます。 これは、シリアル化、逆シリアル化、インポート、およびエクスポートに必須のメソッドです。

最初の作業として、他の型に割り当てる型を定義します。 次に例を示します。

public Type GetDataContractType(Type type)
{
    Console.WriteLine("GetDataContractType");
    if (typeof(Inventory).IsAssignableFrom(type))
    {
        return typeof(InventorySurrogated);
    }
    return type;
}
  • シリアル化では、後で GetObjectToSerialize メソッドを呼び出して、元のインスタンスをサロゲートされたインスタンスに変換する際に、このメソッドによって返されたマッピングが使用されます。

  • 逆シリアル化では、サロゲート型のインスタンスに逆シリアル化する際に、シリアライザーはこのメソッドによって返されたマッピングを使用します。 その後、GetDeserializedObject が呼び出されて、サロゲートされたインスタンスが元の型のインスタンスに変換されます。

  • エクスポートでは、メタデータの生成に使用するデータ コントラクトを取得する際に、このメソッドによって返されたサロゲート型が反映されます。

  • インポートでは、最初の型がサロゲート型に変更されます。このサロゲート型は、参照のサポートなどの目的で使用するデータ コントラクトを取得する際に反映されます。

Type パラメーターは、シリアル化、逆シリアル化、インポート、またはエクスポートするオブジェクトの型です。 サロゲートが型を処理しない場合、GetDataContractType メソッドは入力の型を返す必要があります。 それ以外の場合は、サロゲートされた適切な型を返します。 複数のサロゲート型が存在する場合は、このメソッドで多数のマッピングを定義できます。

GetDataContractType メソッドは、組み込みのデータ コントラクト プリミティブ (Int32String など) に対しては呼び出されません。 その他の型 (配列、ユーザー定義型、その他のデータ構造体など) については、このメソッドは各型に対して呼び出されます。

前の例では、このメソッドは type パラメーターと Inventory が比較可能かどうかをチェックします。 比較可能な場合は、InventorySurrogated に割り当てます。 シリアル化、逆シリアル化、インポート スキーマ、またはエクスポート スキーマを呼び出すときは、常にこの関数が最初に呼び出されて、型間のマッピングが確認されます。

GetObjectToSerialize Method

GetObjectToSerialize メソッドは、元の型のインスタンスをサロゲートされた型のインスタンスに変換します。 これは、シリアル化に必須のメソッドです。

次の手順では、GetObjectToSerialize メソッドを実装して、物理データを元のインスタンスからサロゲートに割り当てる方法を定義します。 次に例を示します。

public object GetObjectToSerialize(object obj, Type targetType)
{
    Console.WriteLine("GetObjectToSerialize");
    if (obj is Inventory)
    {
        InventorySurrogated isur = new InventorySurrogated();
        isur.numpaper = ((Inventory)obj).paper;
        isur.numpencils = ((Inventory)obj).pencils;
        isur.pens = ((Inventory)obj).pens;
        return isur;
    }
    return obj;
}

GetObjectToSerialize メソッドは、オブジェクトをシリアル化するときに呼び出されます。 このメソッドでは、元の型からサロゲートされた型のフィールドにデータを転送します。 フィールドは、サロゲート フィールドに直接割り当てることができます。また、元のデータの操作をサロゲートに格納することもできます。 このメソッドは、フィールドを直接割り当てる場合、サロゲートされたフィールドに格納されたデータに対して操作を実行する場合、サロゲートされたフィールドに元の型の XML を格納する場合などに使用できます。

targetType パラメーターは、メンバーの宣言された型を指します。 このパラメーターは、GetDataContractType メソッドによって返されたサロゲートされた型です。 シリアライザーは、返されるオブジェクトがこの型に割り当て可能であることを強制するわけではありません。 obj パラメーターは、シリアル化するためのオブジェクトであり、必要に応じてサロゲートに変換されます。 サロゲートがオブジェクトを処理しない場合、このメソッドは入力オブジェクトを返す必要があります。 それ以外の場合は、新しいサロゲート オブジェクトが返されます。 オブジェクトが null の場合、サロゲートは呼び出されません。 さまざまなインスタンスの多数のサロゲートのマッピングをこのメソッドで定義できます。

DataContractSerializer の作成時に、オブジェクト参照を保持するように指定できます (詳細については、「シリアル化と逆シリアル化」を参照してください)。これを行うには、コンストラクターの preserveObjectReferences パラメーターを true に設定します。 この場合、サロゲートはオブジェクトに対して 1 回だけ呼び出されます。これは、以降のシリアル化では参照をストリームに書き込むだけだからです。 preserveObjectReferencesfalse に設定すると、インスタンスが発生するたびにサロゲートが呼び出されます。

シリアル化されたインスタンスの型が宣言された型と異なる場合、インスタンスをもう一方の側で逆シリアル化できるように、型情報 (xsi:type など) がストリームに書き込まれます。 このプロセスは、オブジェクトがサロゲートされているかどうかに関係なく発生します。

前述の例では、Inventory インスタンスのデータが InventorySurrogated のデータに変換されます。 オブジェクトの型がチェックされ、サロゲートされた型に変換するために必要な処理が実行されます。 この場合、Inventory クラスの各フィールドは、InventorySurrogated クラスのフィールドに直接コピーされます。

GetDeserializedObject Method

GetDeserializedObject メソッドは、サロゲートされた型のインスタンスを元の型のインスタンスに変換します。 これは、逆シリアル化に必須のメソッドです。

次のタスクとして、サロゲート インスタンスから元のインスタンスに物理データを割り当てる方法を定義します。 次に例を示します。

public object GetDeserializedObject(object obj, Type targetType)
{
    Console.WriteLine("GetDeserializedObject");
    if (obj is InventorySurrogated)
    {
        Inventory invent = new Inventory();
        invent.pens = ((InventorySurrogated)obj).pens;
        invent.pencils = ((InventorySurrogated)obj).numpencils;
        invent.paper = ((InventorySurrogated)obj).numpaper;
        return invent;
    }
    return obj;
}

このメソッドが呼び出されるのは、オブジェクトの逆シリアル化中だけです。 このメソッドは、サロゲート型から元の型に戻す逆シリアル化に必要な逆方向のデータ マッピングを行います。 GetObjectToSerialize メソッドと同様に、フィールド データを直接交換する場合、データに対して操作を実行する場合、XML データを格納する場合などに使用できます。 逆シリアル化するときには、データ変換時の操作の結果として、元のデータから正確なデータ値を必ずしも取得できるとは限りません。

targetType パラメーターは、メンバーの宣言された型を指します。 このパラメーターは、GetDataContractType メソッドによって返されたサロゲートされた型です。 obj パラメーターは、逆シリアル化されたオブジェクトを参照します。 オブジェクトがサロゲートされている場合、オブジェクトを変換して元の型に戻すことができます。 サロゲートがオブジェクトを処理しない場合、このメソッドは入力オブジェクトを返します。 それ以外の場合は、変換が完了すると、逆シリアル化されたオブジェクトが返されます。 複数のサロゲート型が存在する場合、各型とその変換を示すことにより、サロゲートからそれぞれのプライマリ型へのデータ変換を提供できます。

オブジェクトを返す場合、このサロゲートによって返されたオブジェクトで内部オブジェクト テーブルが更新されます。 インスタンスへの以降の参照では、このオブジェクト テーブルからサロゲートされたインスタンスが取得されます。

前の例では、InventorySurrogated 型のオブジェクトが最初の Inventory 型に変換されます。 この場合、データは InventorySurrogated から Inventory の対応するフィールドに直接転送されます。 データ操作は発生しないため、各メンバー フィールドにはシリアル化前と同じ値が格納されます。

GetCustomDataToExport Method

スキーマをエクスポートする場合、GetCustomDataToExport メソッドは省略可能です。 このメソッドは、エクスポートするスキーマに追加データやヒントを挿入するために使用します。 追加データは、メンバー レベルまたは型レベルで挿入できます。 次に例を示します。

public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
    Console.WriteLine("GetCustomDataToExport(Member)");
    System.Reflection.FieldInfo fieldInfo = (System.Reflection.FieldInfo)memberInfo;
    if (fieldInfo.IsPublic)
    {
        return "public";
    }
    else
    {
        return "private";
    }
}

このメソッド (2 つのオーバーロードを持ちます) を使用すると、メンバー レベルまたは型レベルで、追加情報をメタデータに含めることができます。 メンバーが public と private のどちらであるかに関するヒントや、スキーマのエクスポートとインポート全体を通じて保持されるコメントを含めることができます。 このメソッドを使用しない場合、このような情報は失われます。 このメソッドは、メンバーまたは型の挿入や削除を行うのではなく、メンバー レベルまたは型レベルでスキーマに付加的なデータを追加します。

このメソッドのオーバーロードでは、Type (clrtype パラメーター) または MemberInfo (memberInfo パラメーター) を取得できます。 2 番目のパラメーターは、常に Type (dataContractType パラメーター) です。 このメソッドは、サロゲートされた dataContractType 型のすべてのメンバーと型に対して呼び出されます。

どちらのオーバーロードでも、null またはシリアル化可能なオブジェクトを返す必要があります。 null 以外のオブジェクトは、エクスポートするスキーマに注釈としてシリアル化されます。 Type オーバーロードでは、スキーマにエクスポートされる各型は、dataContractType パラメーターのサロゲートされた型と共に、最初のパラメーターでこのメソッドに送信されます。 MemberInfo オーバーロードでは、スキーマにエクスポートされる各メンバーが、2 番目のパラメーターのサロゲートされた型と共に memberInfo パラメーターとして情報を送信します。

GetCustomDataToExport Method (Type, Type)

IDataContractSurrogate.GetCustomDataToExport(Type, Type) メソッドは、すべての型定義のスキーマのエクスポート中に呼び出されます。 このメソッドは、エクスポート時にスキーマ内の型に情報を追加します。 スキーマに含める必要のある追加データがあるかどうかを確認するために、定義済みの各型がこのメソッドに送信されます。

GetCustomDataToExport Method (MemberInfo, Type)

IDataContractSurrogate.GetCustomDataToExport(MemberInfo, Type) は、エクスポートする型のすべてのメンバーのエクスポート中に呼び出されます。 この関数を使用すると、エクスポート時にスキーマに含めるメンバーに関するコメントをカスタマイズできます。 スキーマに付加的なデータを追加する必要があるかどうかをチェックするために、クラスの各メンバーの情報がこのメソッドに送信されます。

前述の例では、サロゲートの各メンバーの dataContractType が検索されます。 検索後、各フィールドの適切なアクセス修飾子が返されます。 このカスタマイズを行わない場合、アクセス修飾子の既定値は public です。 したがって、実際のアクセス制限には関係なく、エクスポートされたスキーマを使用して生成されるコードでは、すべてのメンバーが public として定義されます。 この実装を使用しない場合、numpens メンバーは、サロゲートで private として定義されている場合でも、エクスポートされたスキーマでは public になります。 このメソッドを使用することにより、エクスポートされるスキーマで、アクセス修飾子を private として生成できます。

GetReferencedTypeOnImport Method

このメソッドは、サロゲートの Type を元の型に割り当てます。 スキーマのインポートでは、このメソッドは省略可能です。

スキーマをインポートし、そのコードを生成するサロゲートを作成したら、次のタスクとして、元の型に対してサロゲート インスタンスの型を定義します。

生成されるコードで既存のユーザー型を参照する必要がある場合は、GetReferencedTypeOnImport メソッドを実装してこれを行います。

スキーマをインポートするときに、サロゲートされたデータ コントラクトを型に割り当てるために、すべての型宣言に対してこのメソッドが呼び出されます。 typeName および typeNamespace の各文字列パラメーターでは、サロゲートされた型の名前と名前空間が定義されます。 GetReferencedTypeOnImport の戻り値は、新しい型を生成する必要があるかどうかを判断するために使用されます。 このメソッドは、有効な型または null のいずれかを返す必要があります。 有効な型を返す場合、返される型は生成されるコード内で参照される型として使用されます。 null を返す場合は、型が参照されることはないため、新しい型を作成する必要があります。 複数のサロゲートが存在する場合は、各サロゲート型から最初の型へのマッピングを実行できます。

customData パラメーターは、GetCustomDataToExport から最初に返されたオブジェクトです。 この customData は、サロゲート作成者が、コードを生成するためにインポート時に使用するメタデータに追加情報やヒントを挿入する場合に使用します。

ProcessImportedType メソッド

ProcessImportedType メソッドは、スキーマのインポートから作成された型をカスタマイズします。 このメソッドは省略可能です。

スキーマをインポートするときに、このメソッドを使用すると、インポートする型とコンパイルの情報をカスタマイズできます。 次に例を示します。

public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
    Console.WriteLine("ProcessImportedType");
    foreach (CodeTypeMember member in typeDeclaration.Members)
    {
        object memberCustomData = member.UserData[typeof(IDataContractSurrogate)];
        if (memberCustomData != null
          && memberCustomData is string
          && ((string)memberCustomData == "private"))
        {
            member.Attributes = ((member.Attributes & ~MemberAttributes.AccessMask) | MemberAttributes.Private);
        }
    }
    return typeDeclaration;
}

インポート中に、このメソッドは生成されるすべての型に対して呼び出されます。 指定した CodeTypeDeclaration を変更するか、CodeCompileUnit を変更します。 これには、CodeTypeDeclaration の名前、メンバー、属性、および他のさまざまなプロパティの変更が含まれます。 CodeCompileUnit を処理することにより、ディレクティブ、名前空間、参照アセンブリ、および他のいくつかの側面を変更できます。

CodeTypeDeclaration パラメーターには、DOM コードの型宣言が含まれます。 CodeCompileUnit パラメーターでは、コードの処理に関する変更を行うことができます。 null を返すと、型宣言が破棄されます。 CodeTypeDeclaration を返すと、変更が保持されます。

メタデータのエクスポート時にカスタム データを挿入した場合は、インポート時にこのデータをユーザーに提供して、データを使用できるようにする必要があります。 このカスタム データは、プログラミング モデルのヒントやその他のコメントに使用できます。 各 CodeTypeDeclarationCodeTypeMember インスタンスには、UserData 型にキャストされる IDataContractSurrogate プロパティとしてカスタム データが含まれます。

前述の例では、インポートするスキーマに対して変更が行われています。 コードでは、サロゲートを使用して元の型の private メンバーを保持しています。 スキーマをインポートするときの既定のアクセス修飾子は public です。 したがって、この例のように変更しない限り、サロゲート スキーマのすべてのメンバーは public になります。 エクスポート中に、どのメンバーが private であるかに関するカスタム データがメタデータに挿入されます。 この例では、カスタム データを調べ、アクセス修飾子が private かどうかをチェックした後、属性を設定することで適切なメンバーを private に変更しています。 このカスタマイズを行わない場合、numpens メンバーは、private ではなく public として定義されます。

GetKnownCustomDataTypes メソッド

このメソッドは、スキーマから定義済みのカスタム データ型を取得します。 スキーマのインポートでは、このメソッドは省略可能です。

このメソッドは、スキーマのエクスポートとインポートの開始時に呼び出されます。 このメソッドは、エクスポートまたはインポートするスキーマで使用されるカスタム データ型を返します。 メソッドには、型のコレクションである Collection<T> (customDataTypes パラメーター) が渡されます。 このメソッドでは、既知の型をこのコレクションに追加する必要があります。 既知のカスタム データ型は、DataContractSerializer を使用したカスタム データのシリアル化と逆シリアル化を可能にするために必要となります。 詳細については、「既知のデータ コントラクト型」を参照してください。

サロゲートの実装

WCF でデータ コントラクト サロゲートを使用するには、いくつかの特別な手順に従う必要があります。

シリアル化と逆シリアル化にサロゲートを使用するには

サロゲートを使用してデータのシリアル化と逆シリアル化を実行するには、DataContractSerializer を使用します。 DataContractSerializer は、DataContractSerializerOperationBehavior によって作成されます。 サロゲートも指定する必要があります。

シリアル化と逆シリアル化を実装するには
  1. サービスの ServiceHost のインスタンスを作成します。 詳しい手順については、「基本的な WCF プログラミング」を参照してください。

  2. 指定したサービス ホストの各 ServiceEndpoint について、OperationDescription を検索します。

  3. 操作の動作を検索して、DataContractSerializerOperationBehavior のインスタンスが見つかるかどうかを確認します。

  4. DataContractSerializerOperationBehavior が見つかった場合、DataContractSurrogate プロパティをサロゲートの新しいインスタンスに設定します。 DataContractSerializerOperationBehavior が見つからなかった場合は、新しいインスタンスを作成し、新しい動作の DataContractSurrogate メンバーをサロゲートの新しいインスタンスに設定します。

  5. 最後に、次の例に示すように、この新しい動作を現在の操作の動作に追加します。

    using (ServiceHost serviceHost = new ServiceHost(typeof(InventoryCheck)))
        foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)
        {
            foreach (OperationDescription op in ep.Contract.Operations)
            {
                DataContractSerializerOperationBehavior dataContractBehavior =
                    op.Behaviors.Find<DataContractSerializerOperationBehavior>()
                    as DataContractSerializerOperationBehavior;
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.DataContractSurrogate = new InventorySurrogated();
                }
                else
                {
                    dataContractBehavior = new DataContractSerializerOperationBehavior(op);
                    dataContractBehavior.DataContractSurrogate = new InventorySurrogated();
                    op.Behaviors.Add(dataContractBehavior);
                }
            }
        }
    

メタデータのインポートにサロゲートを使用するには

WSDL や XSD などのメタデータをインポートしてクライアント側のコードを生成する場合、XSD スキーマからコードを生成するコンポーネント (XsdDataContractImporter) にサロゲートを追加する必要があります。 これを行うには、メタデータをインポートする際に使用する WsdlImporter を直接変更します。

メタデータのインポートに使用するサロゲートを実装するには
  1. WsdlImporter クラスを使用して、メタデータをインポートします。

  2. TryGetValue メソッドを使用して、XsdDataContractImporter が定義されているかどうかをチェックします。

  3. TryGetValue メソッドが false を返した場合、新しい XsdDataContractImporter を作成し、Options プロパティを ImportOptions クラスの新しいインスタンスに設定します。 それ以外の場合は、out メソッドの TryGetValue パラメーターによって返されたインポーターを使用します。

  4. XsdDataContractImporterImportOptions が定義されていない場合は、このプロパティを ImportOptions クラスの新しいインスタンスとして設定します。

  5. DataContractSurrogateImportOptionsXsdDataContractImporter プロパティを、サロゲートの新しいインスタンスに設定します。

  6. XsdDataContractImporter クラスから継承した StateWsdlImporter プロパティによって返されたコレクションに、MetadataExporter を追加します。

  7. ImportAllContractsWsdlImporter メソッドを使用して、スキーマ内のすべてのデータ コントラクトをインポートします。 この最後の手順で、サロゲートを呼び出すことによって読み込まれたスキーマからコードが生成されます。

    MetadataExchangeClient mexClient = new MetadataExchangeClient(metadataAddress);
    mexClient.ResolveMetadataReferences = true;
    MetadataSet metaDocs = mexClient.GetMetadata();
    WsdlImporter importer = new WsdlImporter(metaDocs);
    object dataContractImporter;
    XsdDataContractImporter xsdInventoryImporter;
    if (!importer.State.TryGetValue(typeof(XsdDataContractImporter),
        out dataContractImporter))
        xsdInventoryImporter = new XsdDataContractImporter();
    
    xsdInventoryImporter = (XsdDataContractImporter)dataContractImporter;
    xsdInventoryImporter.Options ??= new ImportOptions();
    xsdInventoryImporter.Options.DataContractSurrogate = new InventorySurrogated();
    importer.State.Add(typeof(XsdDataContractImporter), xsdInventoryImporter);
    
    Collection<ContractDescription> contracts = importer.ImportAllContracts();
    

メタデータのエクスポートにサロゲートを使用するには

WCF からサービスのメタデータをエクスポートする場合、既定では、WSDL と XSD スキーマを生成する必要があります。 データ コントラクト型の XSD スキーマを生成するコンポーネント (XsdDataContractExporter) に、サロゲートを追加する必要があります。 これを行うには、IWsdlExportExtension を実装する動作を使用して WsdlExporter を変更するか、メタデータをエクスポートする際に使用する WsdlExporter を直接変更します。

メタデータのエクスポートにサロゲートを使用するには
  1. 新しい WsdlExporter を作成するか、wsdlExporter メソッドに渡された ExportContract パラメーターを使用します。

  2. TryGetValue 関数を使用して、XsdDataContractExporter が定義されているかどうかをチェックします。

  3. TryGetValuefalse を返した場合、XsdDataContractExporter から生成された XML スキーマを使用して新しい WsdlExporter を作成し、StateWsdlExporter プロパティによって返されたコレクションに追加します。 それ以外の場合は、out メソッドの TryGetValue パラメーターによって返されたエクスポーターを使用します。

  4. XsdDataContractExporterExportOptions が定義されていない場合は、Options プロパティを ExportOptions クラスの新しいインスタンスに設定します。

  5. DataContractSurrogateExportOptionsXsdDataContractExporter プロパティを、サロゲートの新しいインスタンスに設定します。 メタデータをエクスポートするための以降の手順を変更する必要はありません。

    WsdlExporter exporter = new WsdlExporter();
    //or
    //public void ExportContract(WsdlExporter exporter,
    // WsdlContractConversionContext context) { ... }
    object dataContractExporter;
    XsdDataContractExporter xsdInventoryExporter;
    if (!exporter.State.TryGetValue(typeof(XsdDataContractExporter),
        out dataContractExporter))
    {
        xsdInventoryExporter = new XsdDataContractExporter(exporter.GeneratedXmlSchemas);
    }
    else
    {
        xsdInventoryExporter = (XsdDataContractExporter)dataContractExporter;
    }
    
    exporter.State.Add(typeof(XsdDataContractExporter), xsdInventoryExporter);
    
    if (xsdInventoryExporter.Options == null)
        xsdInventoryExporter.Options = new ExportOptions();
    xsdInventoryExporter.Options.DataContractSurrogate = new InventorySurrogated();
    

関連項目