ASP.NET MVC CoreのJSONシリアライズではプロパティ名をデフォルトで”camelCase”に変換して吐き出しますが、従来のASP.NET Web APIは”PascalCase”だったので移植する場合などに以前の挙動になってほしいということがあります。
幸いシリアライザの挙動を変更する手段が用意されているのでそこで変更できます。
Startup
のConfigureServices
でASP.NET MVCを利用するためAddMvc
メソッドを呼び出しますが、その際に返ってきたIMvcBuilder
のAddJsonOptions
を呼び出すことで設定を変更できます。
ContractResolver
というプロパティがシリアライズするときに使うリゾルバなのでこれを変更します。従来のASP.NET Web API相当のシリアライザはJSON.NETのデフォルトなのでDefaultContractResolver
をContractResolver
として設定すれば期待の挙動となります。
services .AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); });
他にもJSONのシリアライズをカスタマイズしたい場合にはこの辺りで変更を加えたり、ContractResolverをカスタムしてあげればよいということになります。
ASP.NET MVC Coreはアクションの戻り値が IActionResult
ではない場合、デフォルトでJSONをシリアライズしたものを返します。しかし場合によってはJSONだけでなくXMLで返したいという場合もあるかと思います。
そのようなニーズにこたえるべくフォーマッターを登録することで様々な形式でレスポンスを返すことができる仕組みがあります。ここではXMLで返す方法を説明します。
まずXML用のフォーマッターはNuGetから Microsoft.AspNetCore.Mvc.Formatters.Xml
パッケージをインストールできるのでインストールします。
次にConfigureService
メソッドのASP.NET MVC Coreの設定でXMLのフォーマッターを登録します。
services.AddMvc(options => { options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); options.FormatterMappings.SetMediaTypeMappingForFormat("xml" , "application/xml" ); });
この状態で Accept
ヘッダーフィールドに application/xml
を指定してリクエストするとXMLでレスポンスが返ってくるようになります。簡単ですね。
XMLシリアライザを使うときの注意 XmlDataContractSerializerOutputFormatter
でシリアライズするクラスは引数なしのコンストラクタやプロパティにsetterが必要だったりするのでご注意ください。
この辺りを忘れていると406 Not Acceptableが返ってきて悩むことになります。
URLの .json/.xml のような拡張子で出し分けたい 通常は Accept
によるコンテントネゴシエーションで返すフォーマットを決定すればよいのですが、URLの一部でフォーマットを指定したいというということもあります。例えば、/People/Alice.json
とアクセスした場合はJSONで、/People/Alice.xml
であればXMLを返すといった形です。
これを行うには FormatFilter
フィルター(FormatFilterAttribute
クラス)をコントローラーにつけ、ルーティングに{format}
を足します。例えば以下のようになります。
[FormatFilter ] [Route("[controller]/{id}.{format?}" ) ]public class PeopleController : Controller { [HttpGet ] public Person Get (string id ) { return ; } }
これでフォーマットが指定された場合はそれにマッチするフォーマッターが、指定されていなければデフォルトのフォーマッターが使用されます。
$ curl http://localhost:4743/People/Alice.json {"name":"Alice","age":17} $ curl http://localhost:4743/People/Alice.xml <Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/WebApplication19.Controllers"><Age>17</Age><Name>Alice</Name></Person>
デフォルトのフォーマッターを変更する Acceptやフォーマット名(拡張子)によってフォーマッターを決定できない場合、フォーマッターの登録順で見て行って一番最初に利用できるもの(=必ずしも一番最初ではない)がデフォルトのフォーマッターとなります。
つまり標準では JsonOutputFormatter
が選択されてJSONが返されるわけですが、デフォルトをXMLにしたいという場合もあるかもしれません。そのような場合は一度 JsonOutputFormatter
を抜いて、XMLのフォーマッターを登録してから再度 JsonOutputFormatter
を登録します。
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().First(); options.OutputFormatters.RemoveType<JsonOutputFormatter>(); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); options.OutputFormatters.Add(jsonOutputFormatter);
MessagePack-CSharpを使ってみる 折角なのでレスポンスでMessagePackを返すのも試してみましょう。まあWeb APIでMessagePackを受けたり返したりしたいシーンがどのぐらいあるのかは謎といえば謎なのですが。
C#界最速のシリアライザであるところのMessagePack-CSharp にはASP.NET MVC Core用のフォーマッターが用意されているので使うのは簡単です。
まずNuGetからMessagePack
とMessagePack.AspNetCoreMvcFormatter
をインストールします。
インストールできたらXMLの時と同様に登録します。MessagePackのMIME Typeはapplication/x-msgpack
なので拡張子をマッピングを登録する場合やリクエストのAcceptにはそれを指定してください。折角なのでInputFormatterも登録しておきます。
public void ConfigureServices (IServiceCollection services ) { services.AddMvc(options => { options.FormatterMappings.SetMediaTypeMappingForFormat("msgpack" , "application/x-msgpack" ); options.OutputFormatters.Add(new MessagePackOutputFormatter()); options.InputFormatters.Add(new MessagePackInputFormatter()); }); }
あとは適当なコントローラーとデータを作ります。ここでは /People/Alice
といった形でアクセスできるようなものを作ってみます。
レスポンスに使う Person
クラスはMessagePackでシリアライズできるように MessagePackObject
としてマークしておきます。これはMessagePack-CSharpのお約束です。
[FormatFilter ] [Route("[controller]/{id}.{format?}" ) ]public class PeopleController : Controller { [HttpGet ] public Person Get (string id ) { switch (id) { case "Alice" : return new Person("Alice" , 17 ); case "Karen" : return new Person("Karen" , 17 ); default : return null ; } } } [MessagePackObject ]public class Person { [Key(0) ] public string Name { get ; } [Key(1) ] public int Age { get ; } public Person (string name, int age ) { Name = name; Age = age; } }
あとは /People/Alice.msgpack
または Accept: application/x-msgpack
付きででリクエストを投げることでMessagePackで返ってくるはずです。
$ curl -H 'Accept: application/json' http://localhost:4743/People/Alice 2>/dev/null | od -c 0000000 { " n a m e " : " A l i c e " , 0000020 " a g e " : 1 7 } 0000031 $ curl -H 'Accept: application/x-msgpack' http://localhost:4743/People/Alice 2>/dev/null | od -c 0000000 222 245 A l i c e 021 0000010
ところでMessagePackObject
とかつけるの面倒だなーMapモードでいいんだけどなーということもあるかもしれません。そんな時はContractlessStandardResolver
を使用するとよいでしょう。
services.AddMvc(options => { options.FormatterMappings.SetMediaTypeMappingForFormat("msgpack" , "application/x-msgpack" ); options.OutputFormatters.Add(new MessagePackOutputFormatter(ContractlessStandardResolver.Instance)); options.InputFormatters.Add(new MessagePackInputFormatter(ContractlessStandardResolver.Instance)); });
public class Person { public string Name { get ; } public int Age { get ; } public Person (string name, int age ) { Name = name; Age = age; } }
$ curl -H 'Accept: application/x-msgpack' http://localhost:4743/People/Alice 2>/dev/null | od -c 0000000 202 244 N a m e 245 A l i c e 243 A g e 0000020 021 0000021
このように簡単にMessagePackで返せるようになるので使えそうなときは積極的に使っていきたいですね。
ASP.NET MVC CoreのMvcOptions
にはフォーマッターに関連したRespectBrowserAcceptHeader
プロパティという設定が存在します。この設定は何をするものかというと「ブラウザのようにAcceptに*/*
といったワイルドカードのメディアタイプを乗せて来た場合、デフォルトとするかどうか」です。
例えばそれぞれの設定時には以下のようになります。
falseの場合: Acceptに text/html, application/xml, */*
ならデフォルトのフォーマッター(JSON)、application/xml
ならXMLのフォーマッター
trueの場合: Acceptに text/html, application/xml, */*
ならXMLのフォーマッター、application/xml
ならXMLのフォーマッター
つまりfalseの場合には*/*
やtext/*
のようなものを見つけると、Acceptで指定されたものを全て無視するのと同じになります。基本的にワイルドカードを指定してくるのはブラウザぐらいなのでブラウザのAcceptに敬意を払うかどうかということです。
このプロパティはデフォルトは false
となっていますので、もしブラウザのようなAcceptも受け入れてその通りに判断してほしいというときは true
を設定してください。