ASP.NET Web API 2 でも Content Negotiation は実装されていましたが、Core MVC では進化してさらに便利な機能が追加されていたので、簡単にですが調べてみました。
Formatting Response Data — ASP.NET documentation
ドキュメントも割と揃ってきているので、実際に開発を始められそうな雰囲気がしてきましたね。
Content Negotiation が有効になる条件
ASP.NET Core MVC では Web API 2 の書き方を受け継いでいるので、アクションの戻り値をいろんな型で書けるようになっていますが、Content Negotiation が有効になるのは ObjectResult が使われる場合だけです。
ObjectResult はコントローラヘルパーの Ok や NotFound などを使った時に返される型です。
public IActionResult Get() { var data = new ResponseData { Name = "kazuakix", Age = 50 }; // 戻り値の型は OkObjectResult になる return Ok(data); }
Ok や NotFound などのコントローラヘルパーを使う方法は、HTTP のステータスコードと一対一になっているため、分かりやすいコードが書けるので個人的にはかなりおすすめです。
例外としては任意のオブジェクト型を返すアクションの場合は、自動的に ObjectResult でラップされるため Content Negotiation が有効になります。
public ResponseData Get() { var data = new ResponseData { Name = "kazuakix", Age = 50 }; // ObjectResult で自動的にラップされるので OK return data; }
これでリクエストの Accept ヘッダーの値によって、自動的にレスポンスの Content-Type が選択されるようになります。とは言え、このままだと JSON しか返らないので、この後で Formatter を追加します。
Formatter を追加する
Core MVC ではデフォルトで JSON の Formatter が参照されていますが、公式のパッケージとして XML の Formatter も提供されているので、JSON と XML の両方を扱えるようにしておきます。
MVC 5 では JsonValueProviderFactory などの ValueProvider を拡張して各シリアライズ形式に対応していましたが、Core MVC では Input / Output Formatter という新しい機能が追加されました。
ASP.NET Core ではパッケージ名の規約が分かりやすくなっているので、NuGet で Formatters.Xml と検索すると簡単にヒットします。
当然ながら今後 MessagePack や Protocol Buffers を扱う Formatter が出てきたとしても、NuGet からインストールするだけになるはずです。
パッケージをインストールすると拡張メソッドが追加されるので、AddMvc の呼び出し後にメソッドチェーンで AddXmlSerializerFormatters を呼び出すだけで準備は完了します。
public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddXmlSerializerFormatters(); }
ちなみに、Roslyn CodeFix の謎テクノロジーによって拡張メソッドを先に書くと、該当の NuGet パッケージのインストールを行うことも出来るようになっています。
コードをコピーして持ってきた場合には、こっちの方が簡単に追加できると思います。
リクエストの Content-Type を制限する
これまでの MVC や Web API では Content-Type をあまり気にせずに、とりあえずモデルバインダが処理をしてきましたが、Core MVC では Consumes 属性を使って、許可する Content-Type を簡単に制限できます。
[Consumes("application/json")] public IActionResult Post([FromBody] RequestData data) { return Ok(); }
この場合、リクエストの Content-Type が application/json の場合のみアクションが実行され、それ以外の場合は 415 Unsupported Media Type が返されます。
意図しないフォーマットからのバインディングを防ぐ事が出来ます。
レスポンスの Content-Type を強制する
今度は Consumes とは逆になりまうが、Produces 属性を使うとレスポンスの Content-Type を指定することが出来ます。Content Negotiation を無効にするような設定です。
[Produces("application/xml")] public IActionResult Get() { var data = new ResponseData { Name = "kazuakix", Age = 50 }; return Ok(data); }
この例では Accept ヘッダーの値に依存せず、常に XML としてレスポンスを返すようになります。
動的にレスポンスの Content-Type を切り替える
個人的に Core MVC でも気に入っている機能が、この FormatFilter です。簡単に説明をすると RouteData やクエリ文字列に format というパラメータがあれば、そのフォーマットを選択して返す機能です。
Twitter API のように拡張子でフォーマットを切り替える処理が簡単に実装できるようになっています。format で指定する値は FormatterMappings で自由に定義できるので、独自フォーマットにも対応できます。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "application/xml"); }).AddXmlSerializerFormatters(); }
実際に使う場合には FormatFilter をコントローラかアクションに付けて、ルーティングパラメータに format を含めるようにします。これだけで拡張子のようにフォーマットを指定できます。
[FormatFilter] [Route("api/[controller]")] public class ValuesController : Controller { [HttpGet("{name}.{format?}")] public IActionResult Get(string name) { var data = new ResponseData { Name = name, Age = 50 }; return Ok(data); } }
ちなみにこの書き方で 3 種類の URL に対応できます。未定義の値の場合は 404 が返るようになってます。
/api/values/kazuakix /api/values/kazuakix.xml /api/values/kazuakix?format=json
Content Negotiation 1 つを取り上げてみてもこれぐらいの機能があるので、思ったより全体を追いかけていくのは大変ですが、MVC 5 の頃にあった不満点がかなり解消されているので良い感じです。