データ コントラクト サロゲート は、データ コントラクト モデルに基づいて構築された高度な機能です。 この機能は、ユーザーが型のシリアル化、逆シリアル化、またはメタデータへの投影方法を変更する場合に、型のカスタマイズと置換に使用するように設計されています。 サロゲートを使用できるシナリオの中には、データ コントラクトが型に指定されていない場合、フィールドとプロパティが 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 メソッドは、1 つの型を別の型にマップします。 このメソッドは、シリアル化、逆シリアル化、インポート、およびエクスポートに必要です。
最初のタスクは、他の型にマップされる型を定義することです。 例えば次が挙げられます。
public Type GetDataContractType(Type type)
{
Console.WriteLine("GetDataContractType");
if (typeof(Inventory).IsAssignableFrom(type))
{
return typeof(InventorySurrogated);
}
return type;
}
シリアル化では、このメソッドによって返されるマッピングは、その後、 GetObjectToSerialize メソッドを呼び出して、元のインスタンスを代理インスタンスに変換するために使用されます。
逆シリアル化では、このメソッドによって返されるマッピングは、サロゲート型のインスタンスに逆シリアル化するためにシリアライザーによって使用されます。 その後、 GetDeserializedObject を呼び出して、代理インスタンスを元の型のインスタンスに変換します。
エクスポート時に、このメソッドによって返されるサロゲート型が反映され、メタデータの生成に使用するデータ コントラクトが取得されます。
インポート時に、初期型がサロゲート型に変更され、サポートの参照などの目的で使用するデータ コントラクトを取得するために反映されます。
Type パラメーターは、シリアル化、逆シリアル化、インポート、またはエクスポートされるオブジェクトの型です。 サロゲートが型を処理しない場合、 GetDataContractType メソッドは入力型を返す必要があります。 それ以外の場合は、適切な代理型を返します。 複数のサロゲート型が存在する場合は、このメソッドで多数のマッピングを定義できます。
GetDataContractType メソッドは、Int32やStringなどの組み込みのデータ コントラクト プリミティブには呼び出されません。 配列、ユーザー定義型、その他のデータ構造など、他の型の場合、このメソッドは型ごとに呼び出されます。
前の例では、メソッドは、 type
パラメーターと Inventory
が同等であるかどうかを確認します。 その場合、メソッドはそれを InventorySurrogated
にマップします。 シリアル化、逆シリアル化、インポート スキーマ、またはエクスポート スキーマが呼び出されるたびに、この関数が最初に呼び出され、型間のマッピングが決定されます。
GetObjectToSerialize メソッド
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 回だけ呼び出されます。
preserveObjectReferences
が false
に設定されている場合、インスタンスが検出されるたびにサロゲートが呼び出されます。
シリアル化されたインスタンスの型が宣言された型と異なる場合は、型情報がストリームに書き込まれます。たとえば、インスタンスを逆シリアル化できるように xsi:type
、もう一方の端でインスタンスを逆シリアル化できます。 このプロセスは、オブジェクトが代理かどうかに関係なく発生します。
上記の例では、 Inventory
インスタンスのデータを InventorySurrogated
のデータに変換します。 オブジェクトの型をチェックし、代理型に変換するために必要な操作を実行します。 この場合、 Inventory
クラスのフィールドは、 InventorySurrogated
クラス フィールドに直接コピーされます。
GetDeserializedObject メソッド
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 メソッド
スキーマをエクスポートする場合、 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 つのオーバーロードあり) を使用すると、メンバー レベルまたは型レベルでメタデータに追加情報を含めます。 メンバーがパブリックかプライベートかに関するヒントと、スキーマのエクスポートとインポート中に保持されるコメントを含めることができます。 このような情報は、この方法がないと失われます。 このメソッドは、メンバーまたは型の挿入や削除を行うのではなく、これらのレベルのいずれかでスキーマにデータを追加します。
メソッドはオーバーロードされ、 Type
(clrtype
パラメーター) または MemberInfo (memberInfo
パラメーター) を受け取ることができます。 2 番目のパラメーターは常に Type
(dataContractType
パラメーター) です。 このメソッドは、代理 dataContractType
型のすべてのメンバーと型に対して呼び出されます。
これらのオーバーロードは、 null
またはシリアル化可能なオブジェクトを返す必要があります。 null 以外のオブジェクトは、エクスポートされたスキーマに注釈としてシリアル化されます。
Type
オーバーロードの場合、スキーマにエクスポートされる各型は、dataContractType
パラメーターとしてサロゲートされた型と共に、最初のパラメーターでこのメソッドに送信されます。
MemberInfo
オーバーロードの場合、スキーマにエクスポートされる各メンバーは、2 番目のパラメーターで代理型を持つmemberInfo
パラメーターとして情報を送信します。
GetCustomDataToExport メソッド (Type, Type)
IDataContractSurrogate.GetCustomDataToExport(Type, Type) メソッドは、すべての型定義のスキーマ エクスポート中に呼び出されます。 このメソッドは、エクスポート時にスキーマ内の型に情報を追加します。 定義された各型は、スキーマに含める必要がある追加データがあるかどうかを判断するために、このメソッドに送信されます。
GetCustomDataToExport メソッド (MemberInfo, Type)
IDataContractSurrogate.GetCustomDataToExport(MemberInfo, Type)は、エクスポートされる型のすべてのメンバーのエクスポート中に呼び出されます。 この関数を使用すると、エクスポート時にスキーマに含めるメンバーのコメントをカスタマイズできます。 クラス内のすべてのメンバーの情報は、スキーマに追加データを追加する必要があるかどうかを確認するために、このメソッドに送信されます。
上の例では、サロゲートの各メンバーの dataContractType
を検索します。 次に、各フィールドの適切なアクセス修飾子を返します。 このカスタマイズがないと、アクセス修飾子の既定値はパブリックになります。 したがって、エクスポートされたスキーマを使用して生成されたコードでは、実際のアクセス制限に関係なく、すべてのメンバーがパブリックとして定義されます。 この実装を使用しない場合、メンバー numpens
は、サロゲートでプライベートとして定義されている場合でも、エクスポートされたスキーマでパブリックになります。 このメソッドを使用すると、エクスポートされたスキーマで、アクセス修飾子をプライベートとして生成できます。
GetReferencedTypeOnImport メソッド
このメソッドは、サロゲートの 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
を返す場合、変更は保持されます。
メタデータのエクスポート中にカスタム データを挿入する場合は、使用できるように、インポート時にユーザーに提供する必要があります。 このカスタム データは、プログラミング モデル ヒントやその他のコメントに使用できます。 各 CodeTypeDeclaration
および CodeTypeMember インスタンスには、 UserData プロパティとしてカスタム データが含まれており、 IDataContractSurrogate
型にキャストされます。
上記の例では、インポートされたスキーマに対していくつかの変更が実行されます。 このコードでは、サロゲートを使用して、元の型のプライベート メンバーを保持します。 スキーマのインポート時の既定のアクセス修飾子は public
。 そのため、この例のように変更しない限り、サロゲート スキーマのすべてのメンバーはパブリックになります。 エクスポート時に、カスタム データは、どのメンバーがプライベートであるかに関するメタデータに挿入されます。 この例では、カスタム データを検索し、アクセス修飾子がプライベートであるかどうかを確認し、その属性を設定して適切なメンバーをプライベートに変更します。 このカスタマイズがないと、 numpens
メンバーはプライベートではなくパブリックとして定義されます。
GetKnownCustomDataTypes メソッド
このメソッドは、スキーマから定義されたカスタム データ型を取得します。 このメソッドは、スキーマのインポートでは省略可能です。
このメソッドは、スキーマのエクスポートとインポートの開始時に呼び出されます。 このメソッドは、エクスポートまたはインポートされたスキーマで使用されるカスタム データ型を返します。 このメソッドには、型のコレクションである Collection<T> ( customDataTypes
パラメーター) が渡されます。 メソッドは、このコレクションに既知の型を追加する必要があります。
DataContractSerializerを使用してカスタム データのシリアル化と逆シリアル化を有効にするには、既知のカスタム データ型が必要です。 詳細については、「 データ コントラクトの既知の型」を参照してください。
サロゲートの実装
WCF 内でデータ コントラクト サロゲートを使用するには、いくつかの特別な手順に従う必要があります。
シリアル化と逆シリアル化にサロゲートを使用するには
DataContractSerializerを使用して、サロゲートを使用してデータのシリアル化と逆シリアル化を実行します。 DataContractSerializerは、DataContractSerializerOperationBehaviorによって作成されます。 サロゲートも指定する必要があります。
シリアル化と逆シリアル化を実装するには
サービスの ServiceHost のインスタンスを作成します。 完全な手順については、「 基本的な WCF プログラミング」を参照してください。
指定したサービス ホストのすべての ServiceEndpoint について、その OperationDescriptionを見つけます。
操作の動作を検索して、 DataContractSerializerOperationBehavior のインスタンスが見つかったかどうかを判断します。
DataContractSerializerOperationBehaviorが見つかった場合は、そのDataContractSurrogateプロパティをサロゲートの新しいインスタンスに設定します。 DataContractSerializerOperationBehaviorが見つからない場合は、新しいインスタンスを作成し、新しい動作のDataContractSurrogate メンバーをサロゲートの新しいインスタンスに設定します。
最後に、次の例に示すように、この新しい動作を現在の操作の動作に追加します。
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 を直接変更します。
メタデータのインポート用のサロゲートを実装するには
WsdlImporter クラスを使用してメタデータをインポートします。
XsdDataContractImporterが定義されているかどうかを確認するには、TryGetValue メソッドを使用します。
TryGetValue メソッドが
false
を返す場合は、新しいXsdDataContractImporterを作成し、そのOptionsプロパティを ImportOptions クラスの新しいインスタンスに設定します。 それ以外の場合は、TryGetValue メソッドのout
パラメーターによって返されるインポーターを使用します。XsdDataContractImporterにImportOptionsが定義されていない場合は、プロパティを ImportOptions クラスの新しいインスタンスに設定します。
XsdDataContractImporterのImportOptionsのDataContractSurrogate プロパティをサロゲートの新しいインスタンスに設定します。
WsdlImporterのState プロパティによって返されるコレクションにXsdDataContractImporterを追加します (MetadataExporter クラスから継承されます)。
スキーマ内のすべてのデータ コントラクトをインポートするには、WsdlImporterの ImportAllContracts メソッドを使用します。 最後の手順では、サロゲートを呼び出すことによって読み込まれたスキーマからコードが生成されます。
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 スキーマの両方を生成する必要があります。 データ コントラクト型 ( XsdDataContractExporter) の XSD スキーマの生成を担当するコンポーネントにサロゲートを追加する必要があります。 これを行うには、 IWsdlExportExtension を実装する動作を使用して WsdlExporterを変更するか、メタデータのエクスポートに使用する WsdlExporter を直接変更します。
メタデータのエクスポートにサロゲートを使用するには
新しいWsdlExporterを作成するか、ExportContract メソッドに渡された
wsdlExporter
パラメーターを使用します。TryGetValue関数を使用して、XsdDataContractExporterが定義されているかどうかを確認します。
TryGetValueが
false
を返す場合は、WsdlExporterから生成された XML スキーマを使用して新しいXsdDataContractExporterを作成し、WsdlExporterのState プロパティによって返されるコレクションに追加します。 それ以外の場合は、TryGetValue メソッドのout
パラメーターによって返されるエクスポーターを使用します。XsdDataContractExporterにExportOptionsが定義されていない場合は、Options プロパティを ExportOptions クラスの新しいインスタンスに設定します。
XsdDataContractExporterのExportOptionsのDataContractSurrogate プロパティをサロゲートの新しいインスタンスに設定します。 メタデータをエクスポートするための後続の手順では、変更は必要ありません。
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();