次の方法で共有


ASP.NET Web API でのコンテンツ ネゴシエーション

この記事では、ASP.NET Web API が ASP.NET 4.x のコンテンツ ネゴシエーションを実装する方法について説明します。

HTTP 仕様 (RFC 2616) は、コンテンツ ネゴシエーションを "複数の表現が使用可能な場合に特定の応答に最適な表現を選択するプロセス" と定義しています。HTTP でのコンテンツ ネゴシエーションの主なメカニズムは、次の要求ヘッダーです。

  • 受け入れる: 応答に許容されるメディアの種類 ("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、または "anything" (*/*) を要求しました。 サーバーは、 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 ヘッダーと照合することで、最適な文字エンコードを選択します (存在する場合)。