REST と POX
このサンプルでは、Windows Communication Foundation (WCF) で HTTP トランスポートを使用して "Plain Old XML" (POX) メッセージを送受信する方法を示します。このメッセージは XML ペイロードだけで構成されており、SOAP エンベロープで包まれていません。POX メッセージは、さまざまな種類のクライアントで送受信できます。たとえば、SOAP ベースのプロトコルをネイティブにサポートしていない Web ブラウザも、これに含まれます。POX が適切な選択肢となるのは、HTTP を経由してデータを交換するサービスが、HTTP 以外のトランスポート、要求/応答以外のメッセージ交換パターン、メッセージ ベースのセキュリティ、信頼性、およびトランザクションなど、SOAP と WS の高度なプロトコル機能を使用する必要のない場合です。
POX サービスの実装
このサンプルのサービスでは、非常に基本的な顧客データベースを実装します。コントラクトの面から見た場合、サービスは次のように、Message
を入力として取得して Message
を返す ProcessMessage
という 1 つの操作を公開します。
[ServiceContract]
public interface IUniversalContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message input);
}
ただし、このコントラクトはあまり機能的ではありません。実装するのは、HTTP を使用できる方法でこのコレクションの内容にアクセスするための、次のような基本的なアドレス指定の規則です。
コレクションは、https://localhost:8100/customers にあります。この URI に送信される HTTP GET 要求は、コレクションの内容を、個別のエントリを示す URI のリストとして返します。
コレクション内の各エントリには、顧客 ID をコレクションの URI に追加することによって作成された、一意の URI があります。たとえば、https://localhost:8100/customers/1 は ID 1 の顧客を示します。
エントリの URI が指定されると、HTTP GET 要求をエントリの URI に発行することによって、顧客の XML 表現を取得できます。
エントリを変更するには、PUT を使用して、新しい表現を既存のエントリの URI に適用します。
エントリを追加するには、HTTP POST を使用して、新しいエントリの内容をコレクションの URI に送信します。新しいエントリの URI は、サーバーの応答内の HTT`P ロケーション ヘッダーによって返されます。
エントリを削除するには、エントリの URI に DELETE 要求を送信します。
このアーキテクチャ様式は REST (Representational State Transfer) と呼ばれ、HTTP と POX メッセージを使用して通信するアプリケーションをデザインするための 1 つの方法です。
これをすべて実現するには、まず、公開するコントラクトを実装するサービスを次のように作成する必要があります。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
AddressFilterMode = AddressFilterMode.Prefix)]
class CustomerService : IUniversalContract
{
Dictionary<string, Customer> customerList;
public Message ProcessMessage(Message request) { ... }
}
customerList
ローカル変数には、データベースの内容が格納されます。この変数の内容がそれぞれの要求に保持されるようにするには、ServiceBehavior 属性を使用して InstanceContextMode.Single を指定します。これにより、それぞれの要求で同じ物理サービス インスタンスを使用するように、WCF に通知されます。さらに AddressFilterMode.Prefix も設定します。これは、サービスに実装する階層的アドレス指定構造をサポートします。AddressFilterMode.Prefix により、サービスは、そのエンドポイント アドレスに完全に一致する URI だけでなく、そのアドレスで始まるすべての URI でリッスンします。
サービスは、次の構成を使用して構成されます。
<system.serviceModel>
<bindings>
<customBinding>
<binding name="poxBinding">
<textMessageEncoding messageVersion="None" />
<httpTransport />
</binding>
</customBinding>
</bindings>
<services>
<service name="Microsoft.ServiceModel.Samples.CustomerService">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8100/" />
</baseAddresses>
</host>
<endpoint address="customers"
binding="customBinding"
bindingConfiguration="poxBinding"
contract="Microsoft.ServiceModel.Samples.IUniversalContract" />
</service>
</services>
</system.serviceModel>
この構成により、単一のエンドポイント (https://localhost:8100/customers) を持つ単一のサービス (https://localhost:8100) が設定されます。このエンドポイントは、HTTP トランスポートとテキスト エンコーダを持つカスタム バインディングを使用して通信します。このエンコーダは、MessageVersion.None を使用するように設定されています。これにより、エンコーダは SOAP エンベロープを持たないメッセージを読み取ることができるようになり、応答メッセージを書き込むときに SOAP エンベロープを挿入します。このサンプルでは、単純化してわかりやすくするために、セキュリティで保護されていないトランスポートを経由する通信を示します。セキュリティ保護が必要な場合は、POX アプリケーションで、HTTP トランスポート セキュリティ (HTTPS) が組み込まれたバインディングを使用する必要があります。
ProcessMessage() の実装
ProcessMessage()
を実装し、受信した要求内に含まれている HTTP メソッドに基づいてさまざまなアクションを実行します。これを実現するには、Message で直接公開されない、一部の HTTP プロトコル情報にアクセスする必要があります。しかし、HttpRequestMessageProperty クラスを使用することで、次のように HTTP メソッド (および要求のヘッダー コレクションなど、他の有用なプロトコル要素) を取得できます。
public Message ProcessMessage(Message request)
{
Message response = null;
//The HTTP Method (for example, GET) from the incoming HTTP request
//can be found on the HttpRequestMessageProperty. The MessageProperty
//is added by the HTTP Transport when the message is received.
HttpRequestMessageProperty requestProperties =
(HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
…
}
取得した HttpRequestMessageProperty に基づいて、次のようにさまざまな内部実装のメソッドをディスパッチできます。
if (String.Equals("GET", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = GetCustomer(request);
}
else if (String.Equals("PUT", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = UpdateCustomer(request);
}
else if (String.Equals("POST", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = AddCustomer(request);
}
else if (String.Equals("DELETE", requestProperties.Method,
StringComparison.OrdinalIgnoreCase))
{
response = DeleteCustomer(request);
}
GET と POST は最も一般的な HTTP メソッドです (PUT と DELETE はそれほどではありません) が、HTTP の仕様には、サンプルのサービスでサポートする予定のない HEAD や OPTIONS など、他にもいくつかの動詞が定義されています。さらに、この特定の目的のためのステータス コード (405 メソッドは許可されていません) も定義されています。そのため、サービスの ProcessMessage
には次のロジックがあります。
else
{
//This service does not implement handlers for other HTTP verbs (such as HEAD), so we
//construct a response message and use the HttpResponseMessageProperty to
//set the HTTP status code to 405 (Method Not Allowed) which indicates the client
//used an HTTP verb not supported by the server.
response = Message.CreateMessage(MessageVersion.None, String.Empty, String.Empty);
HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
responseProperty.StatusCode = HttpStatusCode.MethodNotAllowed;
response.Properties.Add( HttpResponseMessageProperty.Name, responseProperty );
}
HttpResponseMessageProperty は HttpRequestMessageProperty とよく似ていますが、ステータス コードやステータス説明など、応答固有のプロパティが含まれる点が異なります。既定では、作成時のメッセージにはこのプロパティはありません。そのため、応答メッセージが返される前に、このプロパティを明示的に Properties コレクションに追加する必要があります。
これ以外のサービス実装のメソッド (GetCustomer や AddCustomer など) は、同様に HttpRequest/HttpResponse メッセージ プロパティを使用するので簡単です。
POX クライアントの実装
サンプルのサービスの REST アーキテクチャにより、このサンプルのクライアントは基本的な HTTP クライアントです。クライアントは、わかりやすいように WCF HTTP トランスポート スタックの上に実装されますが、HttpWebRequest を使用したり (WCF がクライアントで使用できない場合など)、最新の Web ブラウザで提供される XmlHttpRequest のサポートを使用したりしても実装できます。
WCF を使用して生の HTTP 要求を送信すると、メッセージが作成され、HttpRequestMessageProperty の適切な値が設定されます。さらに、必要に応じてエンティティ本体にデータが挿入され、サーバーに送信されます。この処理をより簡単に行うため、次のように基本の HTTP クライアント クラスを記述します。
public class HttpClient : ClientBase<IRequestChannel>
{
public HttpClient( Uri baseUri, bool keepAliveEnabled ) :
this( baseUri.ToString(), keepAliveEnabled )
{
}
public HttpClient( string baseUri, bool keepAliveEnabled ) :
base( HttpClient.CreatePoxBinding( keepAliveEnabled ),
new EndpointAddress( baseUri ) )
{
}
//Other members elided for clarity
}
ServiceModel Metadata Utility Tool (Svcutil.exe) によって WSDL メタデータから生成されるクライアント クラスと同様に、HttpClient
クラスは ClientBase<TChannel> を継承します。クライアントは非常に一般的なコントラクト (IRequestChannel) として実装されますが、ClientBase`1<TChannel> にはさらに多くの有用な機能があります。たとえば、自動的に ChannelFactory<IRequestChannel> を作成し、その有効期限を自動的に管理します。
サービスで使用するバインディングと同様に、HTTP クライアントは次のようにカスタム バインディングを使用して通信します。
private static Binding CreatePoxBinding( bool keepAliveEnabled )
{
TextMessageEncodingBindingElement encoder =
new TextMessageEncodingBindingElement( MessageVersion.None, Encoding.UTF8 );
HttpTransportBindingElement transport = new HttpTransportBindingElement();
transport.ManualAddressing = true;
transport.KeepAliveEnabled = keepAliveEnabled;
return new CustomBinding( new BindingElement[] { encoder, transport } );
}
クライアント バインディングには、HttpTransportBindingElement で ManualAddressing を true に設定するための手順が主に追加されます。通常、ManualAddressing が false に設定されると、トランスポートを経由して送信されるメッセージは、トランスポートの ChannelFactory が作成されたときに提供される URI に送られます。アドレス指定を手動で行う場合、URI が ChannelFactory の URI と同じプレフィックスを持つ限り、トランスポートを経由して送信される個別のメッセージに、要求ごとにさまざまな URI を設定できます。メッセージの送信先となる URI はアプリケーション レイヤで選択します。そのため、ManualAddressing を true に設定することが使用目的に合致します。
HttpClient で最も重要なメソッドは Request です。このメソッドはメッセージを特定の URI に送信してサーバーの応答を返します。GET や DELETE などの HTTP メソッドの場合、データを HTTP メッセージに含めて送信することはできないので、SuppressEntityBody を true に設定する Request() のオーバーロードを次のように定義します。
public Message Request( Uri requestUri, string httpMethod )
{
Message request = Message.CreateMessage( MessageVersion.None, String.Empty );
request.Headers.To = requestUri;
HttpRequestMessageProperty property = new HttpRequestMessageProperty();
property.Method = httpMethod;
property.SuppressEntityBody = true;
request.Properties.Add( HttpRequestMessageProperty.Name, property );
return this.Channel.Request( request );
}
データを要求の本文に含めて送信できる POST や PUT などの動詞をサポートするために、エンティティ本体を表すオブジェクトを受け取る Request() のオーバーロードも次のように定義します。
public Message Request( Uri requestUri, string httpMethod, object entityBody )
{
Message request = Message.CreateMessage( MessageVersion.None, String.Empty, entityBody );
request.Headers.To = requestUri;
HttpRequestMessageProperty property = new HttpRequestMessageProperty();
property.Method = httpMethod;
request.Properties.Add( HttpRequestMessageProperty.Name, property );
return this.Channel.Request( request );
}
entityBody
オブジェクトは直接 Message.CreateMessage()
に渡されるので、WCF の既定の動作では、DataContractSerializer によってこのオブジェクト インスタンスが XML に変換されます。他の動作が必要な場合は、このオブジェクトを Request() に渡す前に、BodyWriter の実装内でラップします。
HttpClient
の実装を終了するには、次のように、プログラミング モデルを作成するいくつかのユーティリティ メソッドを、サポートする動詞の周囲に追加します。
public Message Get( Uri requestUri )
{
return Request( requestUri, "GET" );
}
public Message Post( Uri requestUri, object body )
{
return Request( requestUri, "POST", body );
}
public Message Put( Uri requestUri, object body )
{
return Request( requestUri, "PUT", body );
}
public Message Delete( Uri requestUri )
{
return Request( requestUri, "DELETE" );
}
基本の HTTP ヘルパー クラスができたので、次のようなコードを記述することにより、このクラスを使用してサービスに対して HTTP 要求を行うことができます。
HttpClient client = new HttpClient( collectionUri );
//Get Customer 1 by doing an HTTP GET to the customer's URI
requestUri = new Uri( collectionUri, "1" );
Message response = client.Get( requestUri );
string statusCode = client.GetStatusCode( response );
string statusDescription = client.GetStatusDescription( response );
Customer aCustomer = response.GetBody<Customer>();
サンプルの実行
サンプルを実行するには、最初にサーバー プロジェクトを開始します。このプロジェクトは、コンソール アプリケーションで自己ホスト型サービスを開始します。サーバーは、コンソール ウィンドウで、受信する要求の状態を報告します。
サービス プロジェクトが実行されてメッセージを待機するようになったら、クライアント プロジェクトを開始できます。クライアントは一連の HTTP 要求をサーバーに発行し、HTTP と POX のメッセージを使用していることを示して、サーバーの状態を変更します。クライアントは、具体的には次のアクションを実行します。
顧客 1 を取得してそのデータを表示します。
顧客 1 の名前を "Bob" から "Robert" に変更します。
顧客 1 を再度取得して、サーバーの状態が変更されていることを示します。
Alice と Charlie という名前の、新しい 2 人の顧客を作成します。
サーバーから 顧客 1 を削除します。
サーバーから顧客 1 を再度取得して、Endpoint Not Found 応答を取得します。
リンクの一覧となっている、現在のコレクションの内容を取得します。
コレクション内の各リンクで GET 要求を発行し、コレクションの各要素を取得します。
クライアントのコンソール ウィンドウに、各要求と応答の出力が表示されます。
サンプルを設定、ビルド、および実行するには
「Windows Communication Foundation サンプルの 1 回限りのセットアップの手順」が実行済みであることを確認します。
ソリューションの C# 版または Visual Basic .NET 版をビルドするには、「Windows Communication Foundation サンプルのビルド」の手順に従います。
単一コンピュータ構成か複数コンピュータ構成かに応じて、「Windows Communication Foundation サンプルの実行」の手順に従います。
関連項目
その他の技術情報
How To: Create a Basic Self-Hosted Service
How To: Create a Basic IIS-Hosted Service
Copyright © 2007 by Microsoft Corporation.All rights reserved.