ASP.NET Web API 取得するデータの書式を指定する ~Content Negotiation~
ASP.NET Web API は、デフォルトで Json, Xml, FormUrlEncoded の書式に対応しています。
データを取得する際は、リクエストのヘッダー Accept 値で、どの書式でデータを取得するか指定できます。
また、コードを数行追加すれば、「~/api/movies.json/」のように URL で指定したり、
QueryString「~/api/movies/all?format=json」、カスタムヘッダー値で指定できたりします。
例
実際にやってみます。
下のように「~/values/」で Person データを取得する API を用意します。
public class ValuesController : ApiController { public Person Get() { var person = new Person() { Id = 1, Name = "Taro" }; return person; } } public class Person { public int Id { get; set; } public String Name { get; set; } }
※ FormUrlEncoded は送信用の書式なので、Xml と Json で試します。
URL の一部で指定
URL「~/api/values.json/」でリクエストすると、Json で、
URL「~/api/values.xml/」でリクエストすると、Xml で取得できるようにします。
これには、少しコードの追加が必要です。
WebApiConfig.cs にて、ルーティングの設定と、MediaTypeMapping の設定を追加します。
(MediaTypeMapping が何者かについては後で記述します)
// ※using System.Net.Http.Formatting; が必要です。 public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Formatters.XmlFormatter.AddUriPathExtensionMapping("xml", "application/xml"); config.Formatters.JsonFormatter.AddUriPathExtensionMapping("json", "application/json"); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}.{ext}/{id}", defaults: new { id = RouteParameter.Optional } ); ....
デフォルトのルーティングに、ルートデータ"ext"を追加しています。(必ず ext にする必要があります。)
また、AddUriPathExtensionMappin メソッドにて、XmlFormatter と JsonFormatter に、
"ext"の位置に指定する値(第1引数)と、
どの MediaTypeFormatter を使用するか(第2引数:文字列)を指定しています。
(どの MediaTypeFormatter を使用するか = どの書式でデータを表すか)
※最後のスラッシュを省いて(~/api/values.xml)アクセスすると、404 が返ってきます。
ここでは省略しますが、"api/{controller}/{id}.{ext}"等に対応する場合は、もうちょっとルーティングの設定が必要です。
QueryString で指定
URL「~/api/values?format=xml」でリクエストすると、Json で、
URL「~/api/values?format=json」でリクエストすると、Xml で取得できるようにします。
これも、先と同じようにコードの追加が必要です。
WebApiConfig.cs に下のようにコードを追加します。
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Formatters.XmlFormatter.AddQueryStringMapping("format", "xml", "application/xml"); config.Formatters.JsonFormatter.AddQueryStringMapping("format", "json", "application/json"); ...
AddQueryStringMapping メソッドを使用しています。
引数は、左から、QueryString の名前、値、どの MediaTypeFormatter を使用するか、を指定します。
カスタムヘッダー値で指定
任意のカスタムヘッダー値で指定することもできます。
例えば、「My-Header-Format」に Json が指定されていれば、Json で、
Xml が指定されていれば、Xml で取得できるようにします。
これも同様に WebApiConfig.cs にコードを追加します。
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Formatters.XmlFormatter.AddRequestHeaderMapping("My-Header-Format", "xml", StringComparison.CurrentCultureIgnoreCase, false, "application/xml"); config.Formatters.JsonFormatter.AddRequestHeaderMapping("My-Header-Format", "json", StringComparison.CurrentCultureIgnoreCase, false, "application/json"); ...
今度は、AddRequestHeaderMapping を使用しています。
第3引数の bool は、部分一致とするかどうかの指定です。
以上、デフォルトで用意されているものを使って、
リクエストの値によって、取得するデータの書式を指定する方法でした。
これより下は、その仕組みについてですが…少し細かいです。
仕組み ― Content Negotiation
これらの動作は、Content Negotiation の機能によって働いています。
ContentNegotiation は何者かというと、凄く簡単にいえば、”どうやってデータを表すか決定する人”です。
一方、MediaTypeFormatter は、実際に自分の担当する書式に変換する人で、ContentNegotiation に従います。
壁に貼ってあるライフサイクルポスターで言うと、右下の「結果の変換」の部分です。
(え?貼ってないですか? → Get PDF)
この部分は、下のように流れます。
- ApiController の アクションメソッドの戻り値が、ある型
である。(voidでもなく、HttpResponseMessageでもなく、IHttpActionResult でもなく。) - この戻り値を、HTTP レスポンスの Body に入れてクライアントに返したいが、ある型
のオブジェクトのままでは困る。 - ContentNegotiation が、そのオブジェクトを、どの MediaTypeFormatter を使って Body に格納すればいいか決める。
- 指定された MediaTypeFormatter がオブジェクトを自分の担当する書式へ変換する。
- 変換されたコンテンツは、HTTP レスポンス の Body に格納されて、クライアントへ。
で、この②の ContentNegotiation は、IContentNegotiation インターフェイスで定義されていて、
デフォルトでは、DefaultContentNegotiation が働いています。
(もちろん開発者がこの部分の処理を入れ替えることができる。)
では、このデフォルトの DefaultContentNegotiation はどのように MediaTypeFormatter を決定するのかというと…
以下の優先順位で決定されます。
A MediaTypeFormatter が 戻り値の型に対応しているかどうか?(CanWriteType)
B MediaTypeFormatter がもつ、MediaTypeMappings のルールできめる。
C MediaTypeFormatter が、HTTP リクエストの Accept 値と対応しているか。(SupportedMediaTypes)
特に何もしていないデフォルトの状態で json や xml とかの場合は、A と B の条件はスルーして、C の条件によって決められます。
これが、1つ目の例で動作した、Accept 値によって書式が決まる正体です。
2、3番目の例で紹介した、QueryString や、URL、任意のヘッダーの値で、というのは、
B の条件、MediaTypeMappings によるものです。
MediaTypeMappings は、ルールそのものを表すオブジェクトです。
先の優先順位があるので、例えば、Accept 値が Json で、2つめの例のように QueryString で Xml を指定した場合は、
Xml でデータが返されます。
まとめ
データを取得する際は、リクエストのヘッダー Accept 値で、どの書式でデータを取得するか指定できます。
また、コードを数行追加すれば、URL、QueryString、カスタムヘッダー値でも指定できます。
ContentNegotiation は”どうやってデータを表すか決定する人”で、
MediaTypeMappings は、そのルールの一部、
MediaTypeFormatter は、”実際に自分の担当する書式に変換する人”です。