Windows Communication Foundation (WCF) は、メッセージング インフラストラクチャと考えることができます。 サービス操作では、メッセージの受信、処理、メッセージの送信を行うことができます。 メッセージは操作コントラクトを使用して記述されます。 たとえば、次のコントラクトについて考えてみます。
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(string fromCity, string toCity);
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
End Interface
ここでは、 GetAirfare
操作は、 fromCity
と toCity
に関する情報を含むメッセージを受け取り、数値を含むメッセージを返します。
このトピックでは、操作コントラクトでメッセージを記述できるさまざまな方法について説明します。
パラメーターを使用したメッセージの記述
メッセージを記述する最も簡単な方法は、パラメーター リストと戻り値を使用することです。 前の例では、 fromCity
および toCity
文字列パラメーターを使用して要求メッセージを記述し、float 戻り値を使用して応答メッセージを記述しました。 戻り値だけでは応答メッセージを記述できない場合は、out パラメーターを使用できます。 たとえば、次の操作では、要求メッセージに fromCity
と toCity
があり、数値とその応答メッセージに通貨が含まれています。
[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
さらに、参照パラメーターを使用して、要求と応答メッセージの両方のパラメーター部分を作成できます。 パラメーターは、シリアル化 (XML に変換) できる型である必要があります。 既定では、WCF は DataContractSerializer クラスと呼ばれるコンポーネントを使用してこの変換を実行します。 ほとんどのプリミティブ型 ( int
、 string
、 float
、 DateTime
など) がサポートされています。 通常、ユーザー定義型にはデータ コントラクトが必要です。 詳細については、「 データ コントラクトの使用」を参照してください。
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(Itinerary itinerary, DateTime date);
[DataContract]
public class Itinerary
{
[DataMember]
public string fromCity;
[DataMember]
public string toCity;
}
}
Public Interface IAirfareQuoteService
<OperationContract()>
GetAirfare(itinerary as Itinerary, date as DateTime) as Double
<DataContract()>
Class Itinerary
<DataMember()>
Public fromCity As String
<DataMember()>
Public toCity As String
End Class
End Interface
場合によっては、 DataContractSerializer
が型をシリアル化するのに十分ではありません。 WCF では、代替のシリアル化エンジンである XmlSerializer がサポートされています。パラメーターのシリアル化にも使用できます。
XmlSerializerを使用すると、XmlAttributeAttribute
などの属性を使用して、結果の XML をより詳細に制御できます。 特定の操作またはサービス全体に対して XmlSerializer の使用に切り替えるには、 XmlSerializerFormatAttribute 属性を操作またはサービスに適用します。 例えば次が挙げられます。
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
[XmlSerializerFormat]
float GetAirfare(Itinerary itinerary, DateTime date);
}
public class Itinerary
{
public string fromCity;
public string toCity;
[XmlAttribute]
public bool isFirstClass;
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
<XmlSerializerFormat>
GetAirfare(itinerary as Itinerary, date as DateTime) as Double
End Interface
Class Itinerary
Public fromCity As String
Public toCity As String
<XmlSerializerFormat()>
Public isFirstClass As Boolean
End Class
詳細については、「 XmlSerializer クラスの使用」を参照してください。 ここで示すように XmlSerializer に手動で切り替えることは、そのトピックで詳しく説明するように特定の理由がない限り推奨されないことに注意してください。
.NET パラメーター名をコントラクト名から分離するには、 MessageParameterAttribute 属性を使用し、 Name
プロパティを使用してコントラクト名を設定します。 たとえば、次の操作コントラクトは、このトピックの最初の例と同じです。
[OperationContract]
public float GetAirfare(
[MessageParameter(Name="fromCity")] string originCity,
[MessageParameter(Name="toCity")] string destinationCity);
<OperationContract()>
Function GetAirfare(<MessageParameter(Name := "fromCity")> fromCity As String, <MessageParameter(Name := "toCity")> toCity As String) As Double
空のメッセージの説明
空の要求メッセージは、入力パラメーターまたは参照パラメーターを持たない場合に記述できます。 たとえば、C# では次のようになります。
[OperationContract]
public int GetCurrentTemperature();
たとえば、Visual Basic では次のようになります。
<OperationContract()>
Function GetCurrentTemperature() as Integer
空の応答メッセージは、 void
戻り値の型を持ち、出力パラメーターまたは参照パラメーターを持たない場合に記述できます。 次に例を示します。
[OperationContract]
public void SetTemperature(int temperature);
<OperationContract()>
Sub SetTemperature(temperature As Integer)
これは、次のような一方向の操作とは異なります。
[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);
<OperationContract(IsOneWay:=True)>
Sub SetLightbulbStatus(isOne As Boolean)
SetTemperatureStatus
操作は空のメッセージを返します。 入力メッセージの処理に問題がある場合は、代わりにエラーが返される可能性があります。
SetLightbulbStatus
操作は何も返しません。 この操作からエラー状態を通信する方法はありません。
メッセージ コントラクトを使用したメッセージの記述
1 つの型を使用してメッセージ全体を表したい場合があります。 この目的でデータ コントラクトを使用することもできますが、これを行うにはメッセージ コントラクトを使用することをお勧めします。これにより、結果の XML で不要なレベルの折り返しが回避されます。 さらに、メッセージ コントラクトを使用すると、結果のメッセージをより詳細に制御できます。 たとえば、メッセージ本文に含める情報とメッセージ ヘッダーに含める情報を決定できます。 次の例は、メッセージ コントラクトの使用を示しています。
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
GetAirfareResponse GetAirfare(GetAirfareRequest request);
}
[MessageContract]
public class GetAirfareRequest
{
[MessageHeader] public DateTime date;
[MessageBodyMember] public Itinerary itinerary;
}
[MessageContract]
public class GetAirfareResponse
{
[MessageBodyMember] public float airfare;
[MessageBodyMember] public string currency;
}
[DataContract]
public class Itinerary
{
[DataMember] public string fromCity;
[DataMember] public string toCity;
}
<ServiceContract()>
Public Interface IAirfareQuoteService
<OperationContract()>
Function GetAirfare(request As GetAirfareRequest) As GetAirfareResponse
End Interface
<MessageContract()>
Public Class GetAirfareRequest
<MessageHeader()>
Public Property date as DateTime
<MessageBodyMember()>
Public Property itinerary As Itinerary
End Class
<MessageContract()>
Public Class GetAirfareResponse
<MessageBodyMember()>
Public Property airfare As Double
<MessageBodyMember()> Public Property currency As String
End Class
<DataContract()>
Public Class Itinerary
<DataMember()> Public Property fromCity As String
<DataMember()> Public Property toCity As String
End Class
詳細については、「 メッセージ コントラクトの使用」を参照してください。
前の例では、 DataContractSerializer クラスは既定で引き続き使用されています。 XmlSerializer クラスは、メッセージ コントラクトでも使用できます。 これを行うには、 XmlSerializerFormatAttribute 属性を操作またはコントラクトに適用し、メッセージ ヘッダーと本文メンバーの XmlSerializer クラスと互換性のある型を使用します。
ストリームを使用したメッセージの記述
操作でメッセージを記述するもう 1 つの方法は、 Stream クラスまたはその派生クラスの 1 つを操作コントラクト内で使用するか、メッセージ コントラクト本文メンバーとして使用することです (この場合は唯一のメンバーである必要があります)。 受信メッセージの場合、型は Stream
する必要があります。派生クラスを使用することはできません。
WCF は、シリアライザーを呼び出す代わりに、ストリームからデータを取得して送信メッセージに直接格納するか、受信メッセージからデータを取得してストリームに直接格納します。 次の例は、ストリームの使用を示しています。
[OperationContract]
public Stream DownloadFile(string fileName);
<OperationContract()>
Function DownloadFile(fileName As String) As String
Stream
データとストリーム以外のデータを 1 つのメッセージ本文に結合することはできません。 メッセージ コントラクトを使用して、追加のデータをメッセージ ヘッダーに配置します。 次の例は、操作コントラクトを定義するときにストリームが正しく使用されていないことを示しています。
//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);
'Incorrect:
'<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
次の例は、操作コントラクトを定義するときのストリームの正しい使用方法を示しています。
[OperationContract]
public void UploadFile (UploadFileMessage message);
//code omitted
[MessageContract]
public class UploadFileMessage
{
[MessageHeader] public string fileName;
[MessageBodyMember] public Stream fileData;
}
<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
'Code Omitted
<MessageContract()>
Public Class UploadFileMessage
<MessageHeader()>
Public Property fileName As String
<MessageBodyMember()>
Public Property fileData As Stream
End Class
詳細については、「 大きなデータとストリーミング」を参照してください。
メッセージ クラスの使用
送受信されるメッセージをプログラムで完全に制御するには、次のコード例に示すように、 Message クラスを直接使用します。
[OperationContract]
public void LogMessage(Message m);
<OperationContract()>
Sub LogMessage(m As Message)
これは高度なシナリオであり、「 メッセージ クラスの使用」で詳しく説明されています。
エラー メッセージの記述
戻り値と出力パラメーターまたは参照パラメーターによって記述されるメッセージに加えて、一方向ではない操作では、通常の応答メッセージとエラー メッセージの 2 つ以上のメッセージを返すことができます。 次の操作コントラクトについて考えてみましょう。
[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)
この操作は、 float
番号を含む通常のメッセージ、またはエラー コードと説明を含むエラー メッセージを返す場合があります。 これを実現するには、サービス実装で FaultException をスローします。
FaultContractAttribute属性を使用して、追加のエラー メッセージを指定できます。 追加のエラーは、次のコード例に示すように、 DataContractSerializerを使用してシリアル化できる必要があります。
[OperationContract]
[FaultContract(typeof(ItineraryNotAvailableFault))]
float GetAirfare(string fromCity, string toCity, DateTime date);
//code omitted
[DataContract]
public class ItineraryNotAvailableFault
{
[DataMember]
public bool IsAlternativeDateAvailable;
[DataMember]
public DateTime alternativeSuggestedDate;
}
<OperationContract()>
<FaultContract(GetType(ItineraryNotAvailableFault))>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime) As Double
'Code Omitted
<DataContract()>
Public Class
<DataMember()>
Public Property IsAlternativeDateAvailable As Boolean
<DataMember()>
Public Property alternativeSuggestedDate As DateTime
End Class
これらの追加のエラーは、適切なデータ コントラクト型の FaultException<TDetail> をスローすることによって生成できます。 詳細については、「 例外とエラーの処理」を参照してください。
XmlSerializer クラスを使用して障害を記述することはできません。 XmlSerializerFormatAttributeは、障害コントラクトには影響しません。
派生型の使用
操作またはメッセージ コントラクトで基本型を使用し、実際に操作を呼び出すときに派生型を使用することができます。 この場合、派生型を使用できるようにするには、 ServiceKnownTypeAttribute 属性または代替メカニズムを使用する必要があります。 次の操作を検討してください。
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
Book
とMagazine
の 2 つの型がLibraryItem
から派生しているとします。
IsLibraryItemAvailable
操作でこれらの型を使用するには、次のように操作を変更します。
[OperationContract]
[ServiceKnownType(typeof(Book))]
[ServiceKnownType(typeof(Magazine))]
public bool IsLibraryItemAvailable(LibraryItem item);
または、次のコード例に示すように、既定のDataContractSerializerが使用されている場合は、KnownTypeAttribute属性を使用できます。
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
// code omitted
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryItem
{
//code omitted
}
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
'Code Omitted
<DataContract()>
<KnownType(GetType(Book))>
<KnownType(GetType(Magazine))>
Public Class LibraryItem
'Code Omitted
End Class
XmlSerializerを使用する場合は、XmlIncludeAttribute属性を使用できます。
ServiceKnownTypeAttribute属性は、操作またはサービス全体に適用できます。 KnownTypeAttribute属性と同様に、既知の型のリストを取得するために呼び出すメソッドの型または名前を受け取ります。 詳細については、「 データ コントラクトの既知の型」を参照してください。
使用とスタイルの指定
Web サービス記述言語 (WSDL) を使用してサービスを記述する場合、一般的に使用される 2 つのスタイルは Document と Remote Procedure Call (RPC) です。 ドキュメント スタイルでは、メッセージ本文全体がスキーマを使用して記述され、WSDL は、そのスキーマ内の要素を参照することによって、さまざまなメッセージ本文の部分を記述します。 RPC スタイルでは、WSDL は要素ではなく、各メッセージ 部分のスキーマ型を参照します。 場合によっては、これらのスタイルのいずれかを手動で選択する必要があります。 これを行うには、DataContractFormatAttribute属性を適用し、Style
プロパティを設定するか (DataContractSerializerが使用中の場合)、XmlSerializerFormatAttribute属性にStyle
を設定します (XmlSerializerを使用する場合)。
さらに、 XmlSerializer では、 Literal
と Encoded
の 2 つの形式のシリアル化された XML がサポートされています。
Literal
は最も一般的に受け入れられるフォームであり、 DataContractSerializer がサポートする唯一のフォームです。
Encoded
は、SOAP 仕様のセクション 5 で説明されている従来の形式であり、新しいサービスには推奨されません。
Encoded
モードに切り替えるには、XmlSerializerFormatAttribute属性の Use
プロパティを Encoded
に設定します。
ほとんどの場合、 Style
プロパティと Use
プロパティの既定の設定は変更しないでください。
シリアル化プロセスの制御
さまざまな操作を行って、データのシリアル化方法をカスタマイズできます。
サーバーのシリアル化設定の変更
既定の DataContractSerializer が使用されている場合は、 ServiceBehaviorAttribute 属性をサービスに適用することで、サービスのシリアル化プロセスのいくつかの側面を制御できます。 具体的には、 MaxItemsInObjectGraph
プロパティを使用して、 DataContractSerializer が逆シリアル化するオブジェクトの最大数を制限するクォータを設定できます。
IgnoreExtensionDataObject
プロパティを使用して、ラウンド トリップバージョン管理機能をオフにすることができます。 クォータの詳細については、「 データのセキュリティに関する考慮事項」を参照してください。 ラウンドトリップの詳細については、「 Forward-Compatible データ コントラクト」を参照してください。
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public class MyDataService:IDataService
{
public DataPoint[] GetData()
{
// Implementation omitted
}
}
<ServiceBehavior(MaxItemsInObjectGraph:=100000)>
Public Class MyDataService Implements IDataService
Function GetData() As DataPoint()
‘ Implementation omitted
End Function
End Interface
シリアル化の動作
WCF では、特定の操作に使用されているシリアライザーに応じて自動的に接続される DataContractSerializerOperationBehavior と XmlSerializerOperationBehavior の 2 つの動作を使用できます。 これらの動作は自動的に適用されるため、通常はそれらを認識する必要はありません。
ただし、 DataContractSerializerOperationBehavior
には、シリアル化プロセスのカスタマイズに使用できる MaxItemsInObjectGraph
、 IgnoreExtensionDataObject
、および DataContractSurrogate
プロパティがあります。 最初の 2 つのプロパティは、前のセクションで説明したのと同じ意味を持ちます。
DataContractSurrogate
プロパティを使用すると、シリアル化プロセスをカスタマイズおよび拡張するための強力なメカニズムであるデータ コントラクト サロゲートを有効にすることができます。 詳細については、「 データ コントラクトサロゲート」を参照してください。
DataContractSerializerOperationBehavior
を使用して、クライアントとサーバーの両方のシリアル化をカスタマイズできます。 次の例は、クライアントの MaxItemsInObjectGraph
クォータを増やす方法を示しています。
ChannelFactory<IDataService> factory = new ChannelFactory<IDataService>(binding, address);
foreach (OperationDescription op in factory.Endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior dataContractBehavior =
op.Behaviors.Find<DataContractSerializerOperationBehavior>()
as DataContractSerializerOperationBehavior;
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = 100000;
}
}
IDataService client = factory.CreateChannel();
Dim factory As ChannelFactory(Of IDataService) = New ChannelFactory(Of IDataService)(binding, address)
For Each op As OperationDescription In factory.Endpoint.Contract.Operations
Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()
If dataContractBehavior IsNot Nothing Then
dataContractBehavior.MaxItemsInObjectGraph = 100000
End If
Next
Dim client As IDataService = factory.CreateChannel
セルフホステッドの場合、サービス上の同等のコードを次に示します。
ServiceHost serviceHost = new ServiceHost(typeof(IDataService))
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.MaxItemsInObjectGraph = 100000;
}
}
}
serviceHost.Open();
Dim serviceHost As ServiceHost = New ServiceHost(GetType(IDataService))
For Each ep As ServiceEndpoint In serviceHost.Description.Endpoints
For Each op As OperationDescription In ep.Contract.Operations
Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()
If dataContractBehavior IsNot Nothing Then
dataContractBehavior.MaxItemsInObjectGraph = 100000
End If
Next
Next
serviceHost.Open()
Web ホストの場合は、新しい ServiceHost
派生クラスを作成し、サービス ホスト ファクトリを使用してプラグインする必要があります。
構成でのシリアル化設定の制御
MaxItemsInObjectGraph
とIgnoreExtensionDataObject
は、次の例に示すように、dataContractSerializer
エンドポイントまたはサービスの動作を使用して構成によって制御できます。
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="LargeQuotaBehavior">
<dataContractSerializer
maxItemsInObjectGraph="100000" />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://example.com/myservice"
behaviorConfiguration="LargeQuotaBehavior"
binding="basicHttpBinding" bindingConfiguration=""
contract="IDataService"
name="" />
</client>
</system.serviceModel>
</configuration>
共有型のシリアル化、オブジェクト グラフの保持、およびカスタム シリアライザー
DataContractSerializerは、.NET 型名ではなく、データ コントラクト名を使用してシリアル化します。 これはサービス指向アーキテクチャのテネットと一貫性があり、高度な柔軟性を実現します。.NET 型は、ワイヤ コントラクトに影響を与えずに変更できます。 まれに、実際の .NET 型名をシリアル化して、.NET Framework リモート処理テクノロジと同様に、クライアントとサーバーの間に緊密な結合を導入することが必要になる場合があります。 これは、.NET Framework リモート処理から WCF に移行するときに通常発生するまれなケースを除き、推奨される方法ではありません。 この場合は、DataContractSerializer クラスの代わりに NetDataContractSerializer クラスを使用する必要があります。
通常、 DataContractSerializer はオブジェクト グラフをオブジェクト ツリーとしてシリアル化します。 つまり、同じオブジェクトが複数回参照されている場合は、複数回シリアル化されます。 たとえば、billTo
と shipTo
という名前の Address 型の 2 つのフィールドを持つPurchaseOrder
インスタンスについて考えてみましょう。 両方のフィールドが同じ Address インスタンスに設定されている場合、シリアル化と逆シリアル化の後に同じ Address インスタンスが 2 つ存在します。 これは、XML でオブジェクト グラフを表す標準的な相互運用可能な方法がないためです (Style
とUse
に関する前のセクションで説明したように、XmlSerializerで使用できる従来の SOAP エンコード標準を除く)。 オブジェクト グラフをツリーとしてシリアル化すると、循環参照を含むグラフをシリアル化できないなど、特定の欠点があります。 場合によっては、相互運用できない場合でも、true オブジェクト グラフのシリアル化に切り替える必要があります。 これを行うには、preserveObjectReferences
パラメーターを true
に設定して構築されたDataContractSerializerを使用します。
場合によっては、組み込みのシリアライザーでは、シナリオでは不十分です。 ほとんどの場合、DataContractSerializerとNetDataContractSerializerの両方の派生元であるXmlObjectSerializer抽象化を引き続き使用できます。
前の 3 つのケース (.NET 型の保持、オブジェクト グラフの保持、完全なカスタム XmlObjectSerializer
ベースのシリアル化) はすべて、カスタム シリアライザーを接続する必要があります。 これを実現するには、以下の手順を実行します。
DataContractSerializerOperationBehaviorから派生した独自の動作を記述します。
2 つの
CreateSerializer
メソッドをオーバーライドして、独自のシリアライザー (NetDataContractSerializer、preserveObjectReferences
がtrue
に設定されたDataContractSerializer、または独自のカスタム XmlObjectSerializer) を返します。サービス ホストを開くか、クライアント チャネルを作成する前に、既存の DataContractSerializerOperationBehavior 動作を削除し、前の手順で作成したカスタム派生クラスをプラグインします。
高度なシリアル化の概念の詳細については、「 シリアル化と逆シリアル化」を参照してください。