このトピックでは、サービス コントラクトの種類、定義方法、使用できる操作 (および基になるメッセージ交換への影響)、使用されるデータ型、シナリオの要件を満たす操作の設計に役立つその他の問題について説明します。
サービス コントラクトの作成
サービスは、さまざまな操作を公開します。 Windows Communication Foundation (WCF) アプリケーションで、メソッドを作成し、 OperationContractAttribute 属性でマークすることで操作を定義します。 次に、サービス コントラクトを作成するには、 ServiceContractAttribute 属性でマークされたインターフェイス内で宣言するか、同じ属性でマークされたクラスで定義することによって、操作をグループ化します。 (基本的な例については、「 方法: サービス コントラクトを定義する」を参照してください)。
OperationContractAttribute属性を持たないメソッドはサービス操作ではなく、WCF サービスによって公開されません。
このトピックでは、サービス コントラクトを設計するときの次の決定ポイントについて説明します。
クラスまたはインターフェイスを使用するかどうか。
交換するデータ型を指定する方法。
使用できる交換パターンの種類。
明示的なセキュリティ要件をコントラクトの一部にできるかどうかを指定します。
操作の入力と出力の制限。
クラスまたはインターフェイス
クラスとインターフェイスの両方が機能のグループ化を表すため、両方を使用して WCF サービス コントラクトを定義できます。 ただし、インターフェイスはサービス コントラクトを直接モデル化するため、使用することをお勧めします。 実装がない場合、インターフェイスは特定のシグネチャを持つメソッドのグループ化を定義する以上の操作を行いません。 サービス コントラクト インターフェイスを実装し、WCF サービスを実装しました。
マネージド インターフェイスのすべての利点は、サービス コントラクト インターフェイスに適用されます。
サービス コントラクト インターフェイスは、他の任意の数のサービス コントラクト インターフェイスを拡張できます。
1 つのクラスは、これらのサービス コントラクト インターフェイスを実装することで、任意の数のサービス コントラクトを実装できます。
サービス コントラクトの実装を変更するには、インターフェイスの実装を変更しますが、サービス コントラクトは変わりません。
古いインターフェイスと新しいインターフェイスを実装することで、サービスのバージョンを設定できます。 古いクライアントは元のバージョンに接続しますが、新しいクライアントは新しいバージョンに接続できます。
注
他のサービス コントラクト インターフェイスから継承する場合、名前や名前空間などの操作プロパティをオーバーライドすることはできません。 これを試みる場合は、現在のサービス コントラクトに新しい操作を作成します。
インターフェイスを使用してサービス コントラクトを作成する例については、「 方法: コントラクト インターフェイスを使用してサービスを作成する」を参照してください。
ただし、クラスを使用してサービス コントラクトを定義し、そのコントラクトを同時に実装することができます。 ServiceContractAttributeとOperationContractAttributeをクラスに直接適用してサービスを作成する利点と、クラス上のメソッドは、それぞれ速度とシンプルさです。 欠点は、マネージド クラスは複数の継承をサポートしていないため、一度に実装できるサービス コントラクトは 1 つだけです。 さらに、クラスまたはメソッドのシグネチャを変更すると、そのサービスのパブリック コントラクトが変更されるため、変更されていないクライアントがサービスを使用できなくなる可能性があります。 詳細については、「 サービス コントラクトの実装」を参照してください。
クラスを使用してサービス コントラクトを作成し、同時に実装する例については、「 方法: コントラクト クラスを使用してサービスを作成する」を参照してください。
この時点で、インターフェイスとクラスを使用してサービス コントラクトを定義する違いを理解する必要があります。 次の手順では、サービスとそのクライアントの間で送受信できるデータを決定します。
パラメーターと戻り値
各操作には、戻り値とパラメーターがあります( void
場合でも)。 ただし、あるオブジェクトから別のオブジェクトへの参照を渡すことができるローカル メソッドとは異なり、サービス操作はオブジェクトへの参照を渡しません。 代わりに、オブジェクトのコピーを渡します。
パラメーターまたは戻り値で使用される各型はシリアル化可能である必要があるため、これは重要です。つまり、その型のオブジェクトをバイトストリームに変換し、バイトストリームからオブジェクトに変換できる必要があります。
プリミティブ型は、.NET Framework の多くの型と同様に、既定でシリアル化できます。
注
操作シグネチャのパラメーター名の値はコントラクトの一部であり、大文字と小文字が区別されます。 同じパラメーター名をローカルで使用し、発行されたメタデータの名前を変更する場合は、 System.ServiceModel.MessageParameterAttributeを参照してください。
データ コントラクト
Windows Communication Foundation (WCF) アプリケーションなどのサービス指向アプリケーションは、Microsoft プラットフォームと Microsoft 以外のプラットフォームの両方で、可能な限り多くのクライアント アプリケーションと相互運用できるように設計されています。 可能な限り広い相互運用性を実現するには、データ コントラクトを作成するために、 DataContractAttribute 属性と DataMemberAttribute 属性を使用して型をマークすることをお勧めします。これは、サービス操作が交換するデータを記述するサービス コントラクトの一部です。
データ コントラクトはオプトイン スタイル コントラクトです。データ コントラクト属性を明示的に適用しない限り、型またはデータ メンバーはシリアル化されません。 データ コントラクトは、マネージド コードのアクセス スコープとは無関係です。プライベート データ メンバーはシリアル化して他の場所に送信して、パブリックにアクセスできます。 (データ コントラクトの基本的な例については、「 方法: クラスまたは構造体の基本データ コントラクトを作成する」を参照してください)。WCF は、操作の機能を有効にする基になる SOAP メッセージの定義と、メッセージの本文との間でのデータ型のシリアル化を処理します。 データ型がシリアル化可能である限り、操作を設計するときに、基になるメッセージ交換インフラストラクチャについて考慮する必要はありません。
一般的な WCF アプリケーションでは、 DataContractAttribute 属性と DataMemberAttribute 属性を使用して操作用のデータ コントラクトを作成しますが、他のシリアル化メカニズムを使用することもできます。 標準の ISerializable、 SerializableAttribute、および IXmlSerializable のメカニズムはすべて、データ型のシリアル化を、アプリケーション間で伝達される基になる SOAP メッセージに処理するために機能します。 データ型で特別なサポートが必要な場合は、より多くのシリアル化戦略を採用できます。 WCF アプリケーションでのデータ型のシリアル化の選択肢の詳細については、「 サービス コントラクトでのデータ転送の指定」を参照してください。
パラメーターと戻り値をメッセージ交換にマッピングする
サービス操作は、特定の標準のセキュリティ、トランザクション、およびセッション関連の機能をサポートするためにアプリケーションで必要なデータに加えて、アプリケーション データを前後に転送する SOAP メッセージの基になる交換によってサポートされます。 この場合、サービス操作のシグネチャによって、データ転送と操作に必要な機能をサポートできる特定の基になる メッセージ交換パターン (MEP) が決まります。 WCF プログラミング モデルでは、要求/応答、一方向、双方向のメッセージ パターンの 3 つのパターンを指定できます。
要求/応答
要求/応答パターンは、要求の送信者 (クライアント アプリケーション) が、要求が関連付けられた応答を受信するパターンです。 これは、1 つ以上のパラメーターが操作に渡され、戻り値が呼び出し元に返される操作をサポートするため、既定の MEP です。 たとえば、次の C# コード例は、1 つの文字列を受け取り、文字列を返す基本的なサービス操作を示しています。
[OperationContractAttribute]
string Hello(string greeting);
同等の Visual Basic コードを次に示します。
<OperationContractAttribute()>
Function Hello (ByVal greeting As String) As String
この操作署名は、基になるメッセージ交換の形式を指定します。 相関関係が存在しない場合、WCF は戻り値が意図されている操作を決定できません。
別の基になるメッセージ パターンを指定しない限り、(Visual Basic でNothing
) void
を返すサービス操作であっても、要求/応答メッセージ交換であることに注意してください。 操作の結果は、クライアントが非同期的に操作を呼び出さない限り、通常の場合はメッセージが空であっても、戻りメッセージが受信されるまでクライアントは処理を停止します。 次の C# コード例は、クライアントが応答で空のメッセージを受信するまで戻らない操作を示しています。
[OperationContractAttribute]
void Hello(string greeting);
同等の Visual Basic コードを次に示します。
<OperationContractAttribute()>
Sub Hello (ByVal greeting As String)
前の例では、操作の実行に時間がかかる場合にクライアントのパフォーマンスと応答性が低下する可能性がありますが、 void
を返す場合でも、要求/応答操作に利点があります。 最も明白なのは、SOAP エラーが応答メッセージで返されることです。これは、通信中でも処理中でも、サービス関連のエラー状態が発生したことを示します。 サービス コントラクトで指定された SOAP エラーは、 FaultException<TDetail> オブジェクトとしてクライアント アプリケーションに渡されます。ここで、型パラメーターはサービス コントラクトで指定された型です。 これにより、WCF サービスのエラー状態に関するクライアントへの通知が簡単になります。 例外、SOAP エラー、エラー処理の詳細については、「 コントラクトとサービスでのエラーの指定と処理」を参照してください。 要求/応答サービスとクライアントの例については、「 方法: Request-Reply コントラクトを作成する」を参照してください。 要求/応答パターンに関する問題の詳細については、「 Request-Reply サービス」を参照してください。
一方向
WCF サービス アプリケーションのクライアントが操作の完了を待機せず、SOAP エラーを処理しない場合、操作では一方向メッセージ パターンを指定できます。 一方向操作とは、WCF がメッセージをネットワークに書き込んだ後にクライアントが操作を呼び出して処理を続行する操作です。 通常、これは、送信メッセージで送信されるデータが非常に大きい場合を除き、クライアントは (データの送信エラーがない限り) ほぼ即座に実行を継続することを意味します。 この種類のメッセージ交換パターンは、クライアントからサービス アプリケーションへのイベントのような動作をサポートします。
1 つのメッセージが送信され、何も受信されないメッセージ交換では、 void
以外の戻り値を指定するサービス操作をサポートできません。この場合、 InvalidOperationException 例外がスローされます。
また、戻りメッセージは、処理中または通信中のエラーを示す SOAP エラーが返されない可能性があることも意味します。 (操作が一方向の操作である場合にエラー情報を伝達するには、双方向メッセージ交換パターンが必要です)。
void
を返す操作の一方向メッセージ交換を指定するには、次の C# コード例のように、IsOneWay プロパティを true
に設定します。
[OperationContractAttribute(IsOneWay=true)]
void Hello(string greeting);
同等の Visual Basic コードを次に示します。
<OperationContractAttribute(IsOneWay := True)>
Sub Hello (ByVal greeting As String)
このメソッドは、上記の要求/応答の例と同じですが、true
にIsOneWay プロパティを設定すると、メソッドは同じですが、サービス操作は戻りメッセージを送信せず、クライアントは送信メッセージがチャネル レイヤーに渡されるとすぐに戻ります。 例については、「 方法: One-Way コントラクトを作成する」を参照してください。 一方向パターンの詳細については、「 One-Way サービス」を参照してください。
二連式
双方向パターンは、サービスとクライアントの両方が、一方向または要求/応答メッセージングを使用して個別にメッセージを送信する機能によって特徴付けられます。 この形式の双方向通信は、クライアントに直接通信する必要があるサービスや、イベントのような動作など、メッセージ交換の両側に非同期エクスペリエンスを提供する場合に役立ちます。
双方向パターンは、クライアントと通信するための追加のメカニズムにより、要求/応答パターンまたは一方向パターンよりも少し複雑です。
双方向コントラクトを設計するには、コールバック コントラクトを設計し、そのコールバック コントラクトの型を、サービス コントラクトをマークするServiceContractAttribute属性のCallbackContract プロパティに割り当てる必要もあります。
双方向パターンを実装するには、クライアントで呼び出されるメソッド宣言を含む 2 つ目のインターフェイスを作成する必要があります。
サービスを作成する例と、そのサービスにアクセスするクライアントについては、「 方法: 双方向コントラクトを作成する 」および「 方法: 双方向コントラクトを使用してサービスにアクセスする」を参照してください。 動作サンプルについては、「 二重」を参照してください。 双方向コントラクトの使用に関する問題の詳細については、「 双方向サービス」を参照してください。
注意事項
サービスは、双方向メッセージを受信すると、その受信メッセージの ReplyTo
要素を調べて、応答を送信する場所を決定します。 メッセージの受信に使用されるチャネルがセキュリティで保護されていない場合、信頼されていないクライアントは、ターゲット コンピューターの ReplyTo
で悪意のあるメッセージを送信し、そのターゲット コンピューターのサービス拒否 (DOS) につながる可能性があります。
Out パラメーターと Ref パラメーター
ほとんどの場合、 in
パラメーター (Visual Basic ではByVal
) と out
パラメーターと ref
パラメーター (Visual Basic ではByRef
) を使用できます。
out
パラメーターとref
パラメーターの両方が、操作からデータが返されることを示しているため、次のような操作シグネチャは、操作シグネチャがvoid
を返す場合でも、要求/応答操作が必要であることを指定します。
[ServiceContractAttribute]
public interface IMyContract
{
[OperationContractAttribute]
public void PopulateData(ref CustomDataType data);
}
同等の Visual Basic コードを次に示します。
<ServiceContractAttribute()> _
Public Interface IMyContract
<OperationContractAttribute()> _
Public Sub PopulateData(ByRef data As CustomDataType)
End Interface
唯一の例外は、署名が特定の構造を持つケースです。 たとえば、 NetMsmqBinding バインディングを使用してクライアントと通信できるのは、操作の宣言に使用するメソッドが void
を返す場合のみです。戻り値、 ref
、 out
パラメーターのいずれであっても、出力値を指定することはできません。
さらに、 out
または ref
パラメーターを使用するには、操作に、変更されたオブジェクトを返す基になる応答メッセージが必要です。 操作が一方向の操作の場合は、実行時に InvalidOperationException 例外がスローされます。
コントラクトのメッセージ保護レベルを指定する
コントラクトを設計するときは、コントラクトを実装するサービスのメッセージ保護レベルも決定する必要があります。 これは、コントラクトのエンドポイントのバインディングにメッセージ セキュリティが適用されている場合にのみ必要です。 バインディングのセキュリティがオフになっている場合 (つまり、システム提供のバインディングで System.ServiceModel.SecurityMode が値 SecurityMode.Noneに設定されている場合)、コントラクトのメッセージ保護レベルを決定する必要はありません。 ほとんどの場合、メッセージ レベルのセキュリティが適用されたシステム提供のバインドでは十分な保護レベルが提供され、各操作または各メッセージの保護レベルを考慮する必要はありません。
保護レベルは、サービスをサポートするメッセージ (またはメッセージ パーツ) が署名、署名、暗号化、または署名または暗号化なしで送信されるかどうかを指定する値です。 保護レベルは、サービス レベル、特定の操作、その操作内のメッセージ、またはメッセージ部分など、さまざまなスコープで設定できます。 1 つのスコープで設定された値は、明示的にオーバーライドされない限り、より小さいスコープの既定値になります。 バインディング構成でコントラクトに必要な最小保護レベルを指定できない場合は、例外がスローされます。 また、コントラクトで保護レベルの値が明示的に設定されていない場合、バインディング構成は、バインディングにメッセージ セキュリティがある場合にすべてのメッセージの保護レベルを制御します。 これが既定の動作です。
Von Bedeutung
コントラクトのさまざまなスコープを ProtectionLevel.EncryptAndSign の完全な保護レベル未満に明示的に設定するかどうかを決定することは、通常、ある程度のセキュリティを取引してパフォーマンスを向上させる決定です。 このような場合、意思決定は、操作と、それらが交換するデータの価値を中心に展開する必要があります。 詳細については、「サービスの セキュリティ保護」を参照してください。
たとえば、次のコード例では、コントラクトの ProtectionLevel プロパティまたは ProtectionLevel プロパティは設定されません。
[ServiceContract]
public interface ISampleService
{
[OperationContractAttribute]
public string GetString();
[OperationContractAttribute]
public int GetInt();
}
同等の Visual Basic コードを次に示します。
<ServiceContractAttribute()> _
Public Interface ISampleService
<OperationContractAttribute()> _
Public Function GetString()As String
<OperationContractAttribute()> _
Public Function GetData() As Integer
End Interface
既定のWSHttpBinding (既定のSystem.ServiceModel.SecurityModeMessage) を使用してエンドポイント内のISampleService
実装と対話する場合、既定の保護レベルであるため、すべてのメッセージが暗号化され、署名されます。 ただし、既定のBasicHttpBinding (既定のSecurityModeであるNone) でISampleService
サービスを使用すると、すべてのメッセージがテキストとして送信されます。このバインディングのセキュリティがないため、保護レベルは無視されます (つまり、メッセージは暗号化も署名もされません)。
SecurityModeがMessageに変更された場合、これらのメッセージは暗号化され、署名されます (バインドの既定の保護レベルになるため)。
コントラクトの保護要件を明示的に指定または調整する場合は、サービス コントラクトで必要なレベルに ProtectionLevel プロパティ (または、 ProtectionLevel
プロパティのいずれか小さいスコープ) を設定します。 この場合、明示的な設定を使用するには、使用されるスコープに対して少なくともその設定をサポートするためにバインディングが必要です。 たとえば、次のコード例では、GetGuid
操作に対して 1 つのProtectionLevel値を明示的に指定します。
[ServiceContract]
public interface IExplicitProtectionLevelSampleService
{
[OperationContractAttribute]
public string GetString();
[OperationContractAttribute(ProtectionLevel=ProtectionLevel.None)]
public int GetInt();
[OperationContractAttribute(ProtectionLevel=ProtectionLevel.EncryptAndSign)]
public int GetGuid();
}
同等の Visual Basic コードを次に示します。
<ServiceContract()> _
Public Interface IExplicitProtectionLevelSampleService
<OperationContract()> _
Public Function GetString() As String
End Function
<OperationContract(ProtectionLevel := ProtectionLevel.None)> _
Public Function GetInt() As Integer
End Function
<OperationContractAttribute(ProtectionLevel := ProtectionLevel.EncryptAndSign)> _
Public Function GetGuid() As Integer
End Function
End Interface
このIExplicitProtectionLevelSampleService
コントラクトを実装し、既定のWSHttpBinding (既定のSystem.ServiceModel.SecurityModeMessage) を使用するエンドポイントを持つサービスには、次の動作があります。
GetString
操作メッセージは暗号化され、署名されます。GetInt
操作メッセージは、暗号化されず、署名されていない (プレーンな) テキストとして送信されます。GetGuid
操作System.Guidは、暗号化および署名されたメッセージで返されます。
保護レベルとその使用方法の詳細については、「 保護レベルについて」を参照してください。 セキュリティの詳細については、「サービスの セキュリティ保護」を参照してください。
その他の操作署名の要件
一部のアプリケーション機能には、特定の種類の操作署名が必要です。 たとえば、 NetMsmqBinding バインディングは永続的なサービスとクライアントをサポートします。この場合、アプリケーションは通信の途中で再起動し、メッセージを見逃さずに中断したところから再開できます。 (詳細については、「 WCF のキュー」を参照してください)。ただし、永続的な操作では、 in
パラメーターを 1 つだけ受け取る必要があり、戻り値はありません。
もう 1 つの例として、操作での Stream 型の使用があります。
Stream パラメーターにはメッセージ本文全体が含まれているため、入力または出力 (ref
パラメーター、out
パラメーター、または戻り値) がStream型である場合は、操作で指定された唯一の入力または出力である必要があります。 さらに、パラメーターまたは戻り値の型は、 Stream、 System.ServiceModel.Channels.Message、または System.Xml.Serialization.IXmlSerializableのいずれかである必要があります。 ストリームの詳細については、「 大きなデータとストリーミング」を参照してください。
名前、名前空間、難読化
コントラクトと操作の定義における .NET 型の名前と名前空間は、コントラクトが WSDL に変換されたとき、およびコントラクト メッセージが作成および送信されるときに重要です。 そのため、サービス コントラクトの名前と名前空間は、ServiceContractAttribute、OperationContractAttribute、DataContractAttribute、DataMemberAttribute、その他のコントラクト属性など、すべてのサポート コントラクト属性のName
プロパティとNamespace
プロパティを使用して明示的に設定することを強くお勧めします。
その結果、名前と名前空間が明示的に設定されていない場合、アセンブリで IL 難読化を使用すると、コントラクト型の名前と名前空間が変更され、通常は失敗する WSDL とワイヤ交換が変更されます。 コントラクト名と名前空間を明示的に設定せず、難読化を使用する場合は、 ObfuscationAttribute 属性と ObfuscateAssemblyAttribute 属性を使用して、コントラクト型の名前と名前空間が変更されないようにします。