きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

DapperのQuery<dynamic>()の戻りをASP.NET MVCのJsonResultでいい感じで返す

本エントリはASP.NET Advent Calendar 2014 4日目対応エントリです。

小ネタというか完全誰得ネタですが、割と悩んだのでメモがてら。

 

やりたいこと

まとまりきらないタイトルであれですが、やりたいことはそのまんまです。

例えば以下のような結果のレコードを返すクエリをDBに発行したとします。

name age
Taro 20
Jiro 25

 

このクエリ結果をこんな感じのJSON投げ返してやる、というのが目標です。

[
  { "name": "Taro", "age": 20 },
  { "name": "Jiro", "age": 25 }
]

 

DapperのQuery()の戻りをそのまま返してみる

では、実際にやってみます。 まずはDapperを使ってデータを取得して、そのままIList<dynamic>で返すメソッドを作ります。

private IList<dynamic> GetDynamicRecords()
{
  var conStr = ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
  using (var con = new System.Data.SqlClient.SqlConnection(conStr))
  {
    con.Open();
    var sql = @"select 'Taro' as name, 20 as age union select 'Jiro' as name, 25 as age;";
    var records = con.Query<dynamic>(sql);
    return records.ToList();
  }
}

この戻りをそのままJsonResultで返すASP.NET MVCのコントローラーのアクションメソッドを次のように書きます。

public JsonResult Default()
{
  var json = new JsonResult()
  {
    Data = GetDynamicRecords(),
    JsonRequestBehavior = JsonRequestBehavior.AllowGet
  };
  return json;
}

すると、結果として取れるJSONはこんな感じになります。

[
  [
    { "Key": "name", "Value": "Taro" },
    { "Key": "age",  "Value": 20 }
  ],
  [
    { "Key": "name", "Value": "Jiro" },
    { "Key": "age",  "Value": 25 }
  ]
]

あれ?ちょっと残念な感じ……

 

対処方法:Json.NETを使う

こうなる理由は…まあとりあえずおいておいて*1、どうにか最初の例示した形で欲しいわけです。

いろいろ試行錯誤したところ、ASP.NET MVCのJsonResultクラスが標準で使っているJavaScriptSerializerではなく、ASP.NET Web APIが標準で使っているJSON.NETのシリアライザを使うといい感じ(というか僕が欲しい形)のJSONが得られることがわかりました*2

ということは、JSON.NETを使ったJsonResultの互換クラスを使って、さくっと差し替えればいいわけです。

ということで、以下のように、JsonResultを継承したJsonNetResultなるクラスを作り、ExecuteResultメソッドをoverrideしてJson.NETでシリアライズするようにしてやりましょう。

とりあえず本家ASP.NET MVCのJsonResultクラスの実装を参考に、シリアライズ部分だけJson.NETに差し替えた感じです。

public class JsonNetResult : JsonResult
{
  public override void ExecuteResult(ControllerContext context)
  {
    if (context == null)
    {
      throw new ArgumentNullException("context");
    }
    if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
        String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
    {
      throw new InvalidOperationException("JSON GET is not allowed");
    }

    HttpResponseBase response = context.HttpContext.Response;

    if (!String.IsNullOrEmpty(ContentType))
    {
      response.ContentType = ContentType;
    }
    else
    {
      response.ContentType = "application/json";
    }
    if (ContentEncoding != null)
    {
      response.ContentEncoding = ContentEncoding;
    }
    if (Data != null)
    {
      response.Write(JsonConvert.SerializeObject(this.Data));
    }
  }
}

これを利用するアクションメソッドはこんな感じ。JsonResultをJsonNetResultにそのまま差し替えるだけですね。

public JsonResult JsonNet()
{
  var json = new JsonNetResult()
    {
      Data = GetDynamicRecords(),
      JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
  return json;
}

 

実行するといい感じで結果が取れました。めでたしめでたし。

[
  { "name": "Taro", "age": 20 },
  { "name": "Jiro", "age": 25 }
]

 

まとめ?

はたしてこの情報を欲する人がどれだけ居るのか分りません。……が、他にもExpandoObjectを使って自作したオブジェクトをシリアライズしようとしても同じことになるので、その場合にもこの手法は有効だと思います。

今回のサンプル実装をGithubにあげておきました:

また、せっかくJson.NETに差し替えるのであれば、Json.NETのもつ機能を有効に生かす実装も可能です。そのあたりについては、

Replacing MVC JavascriptSerializer with JSON.NET JsonSerializer

や、北陸のエース@xin9leさんによる

ASP.NET MVCでCamel CaseなJSONを出力する

などが参考になるのではないかと思います。

*1:dynamic型といっても内部的にはDapperのprivateな型のDapperRow型でこの中が…とか色々

*2:MVCじゃなくてWeb API使ってればよかったんじゃあ…』というのも一瞬頭をよぎりました……が、諸事情でWebAPIではなくてMVCを使っているのにも理由があったわけで