ASP.NET Web API でのコンテンツ ネゴシエーション
この記事では、ASP.NET Web API で ASP.NET 4.x のコンテンツ ネゴシエーションを実装する方法について説明します。
HTTP 仕様 (RFC 2616) では、コンテンツ ネゴシエーションは "複数の表現を使用できるときに、特定の応答に最適な表現を選択するプロセス" と定義されています。HTTP でのコンテンツ ネゴシエーションの主なメカニズムは、次の要求ヘッダーです。
- Accept: 応答に許容されるメディア タイプ ("application/json"、"application/xml"、"application/vnd.example+xml" のようなカスタム メディア タイプなど)
- Accept-Charset: 許容される文字セット (UTF-8、ISO 8859-1 など)。
- Accept-Encoding: 許容されるコンテンツ エンコード (gzip など)。
- Accept-Language: 優先される自然言語 ("en-us" など)。
サーバーは、HTTP 要求の他の部分を調べることもできます。 たとえば、要求に X-Requested-With ヘッダー (AJAX 要求を示します) が含まれていて、Accept ヘッダーがない場合、サーバーは既定で JSON になる可能性があります。
この記事では、Web API で Accept と Accept-Charset ヘッダーを使う方法について説明します。 (現時点では、Accept-Encoding または Accept-Language の組み込みサポートはありません)。
シリアル化
Web API コントローラーが CLR 型としてリソースを返す場合、パイプラインは戻り値をシリアル化して HTTP 応答本文に書き込みます。
たとえば、次のようなコントローラー アクションについて考えます。
public Product GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
クライアントは、次の HTTP 要求を送信する場合があります。
GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01
サーバーは、応答で次を送信する可能性があります。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57
Connection: Close
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
この例では、クライアントは JSON、Javascript、または "任意" (*/*) を要求しました。 サーバーは、Product
オブジェクトの JSON 表現で応答しました。 応答の Content-Type ヘッダーが "application/json" に設定されていることに注意してください。
コントローラーは、HttpResponseMessage オブジェクトを返すこともできます。 応答本文に CLR オブジェクトを指定するには、CreateResponse 拡張メソッドを呼び出します。
public HttpResponseMessage GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, item);
}
このオプションを使うと、応答の詳細をいっそう制御できます。 状態コードの設定や、HTTP ヘッダーの追加などを行うことができます。
リソースをシリアル化するオブジェクトは、"メディア フォーマッタ" と呼ばれます。 メディア フォーマッタは、MediaTypeFormatter クラスから派生します。 Web API には XML と JSON 用のメディア フォーマッタが用意されており、カスタム フォーマッタを作成して他のメディア タイプをサポートできます。 カスタム フォーマッタの作成については、メディア フォーマッタに関する記事をご覧ください。
コンテンツ ネゴシエーションのしくみ
まず、パイプラインは HttpConfiguration オブジェクトから IContentNegotiator サービスを取得します。 また、HttpConfiguration.Formatters コレクションからメディア フォーマッタのリストを取得します。
次に、パイプラインは IContentNegotiator.Negotiate を呼び出して、次のものを渡します。
- シリアル化するオブジェクトの型
- メディア フォーマッタのコレクション
- HTTP 要求
Negotiate メソッドは、次の 2 つの情報を返します。
- 使用するフォーマッタ
- 応答のメディア タイプ
フォーマッタが見つからない場合、Negotiate メソッドは null を返し、クライアントは HTTP エラー 406 (受け入れ不可) を受け取ります。
次のコードでは、コントローラーがコンテンツ ネゴシエーションを直接呼び出す方法が示されています。
public HttpResponseMessage GetProduct(int id)
{
var product = new Product()
{ Id = id, Name = "Gizmo", Category = "Widgets", Price = 1.99M };
IContentNegotiator negotiator = this.Configuration.Services.GetContentNegotiator();
ContentNegotiationResult result = negotiator.Negotiate(
typeof(Product), this.Request, this.Configuration.Formatters);
if (result == null)
{
var response = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
throw new HttpResponseException(response));
}
return new HttpResponseMessage()
{
Content = new ObjectContent<Product>(
product, // What we are serializing
result.Formatter, // The media formatter
result.MediaType.MediaType // The MIME type
)
};
}
このコードで行われることは、パイプラインが自動的に実行することと同じです。
既定のコンテンツ ネゴシエーター
DefaultContentNegotiator クラスは、IContentNegotiator の既定の実装を提供します。 複数の条件を使ってフォーマッタを選びます。
第一に、フォーマッタは型をシリアル化できる必要があります。 これは、MediaTypeFormatter.CanWriteType を呼び出して検証されます。
次に、コンテンツ ネゴシエーターは各フォーマッタを調べて、HTTP 要求とどの程度一致するかを評価します。 一致を評価するため、コンテンツ ネゴシエーターはフォーマッタの 2 つの部分を調べます。
- SupportedMediaTypes コレクションには、サポートされているメディア タイプの一覧が含まれます。 コンテンツ ネゴシエーターは、このリストと要求の Accept ヘッダーとの照合を試みます。 Accept ヘッダーは範囲を含むことができることに注意してください。 たとえば、"text/plain" は text/* または */* と一致します。
- MediaTypeMappings コレクションには、MediaTypeMapping オブジェクトのリストが含まれます。 MediaTypeMapping クラスでは、HTTP 要求とメディア タイプを照合する一般的な方法が提供されます。 たとえば、カスタム HTTP ヘッダーを特定のメディア タイプにマップできます。
複数の一致がある場合は、最も高い品質係数を持つ一致が選ばれます。 次に例を示します。
Accept: application/json, application/xml; q=0.9, */*; q=0.1
この例では、application/json は暗黙的な品質係数が 1.0 であるため、application/xml より優先されます。
一致するものが見つからない場合、コンテンツ ネゴシエーターは要求本文のメディア タイプ (存在する場合) で照合を試みます。 たとえば、要求に JSON データが含まれている場合、コンテンツ ネゴシエーターは JSON フォーマッタを探します。
それでも一致がない場合、コンテンツ ネゴシエーターは、その型をシリアル化できる最初のフォーマッタを単純に選びます。
文字エンコードの選択
フォーマッタが選ばれた後、コンテンツ ネゴシエーターは、フォーマッタの SupportedEncodings プロパティを調べ、要求内の Accept-Charset ヘッダー (存在する場合) と照合して、最適な文字エンコードを選びます。