argius note

プログラミング関連

C#でHTTP経由でJSONを取得して値を取り出す

サンプル作ったらボツになったのでここに書くことにします。


HTTPで結果をJSONで返すWebサービスがあって、そこにコンソールアプリからアクセスするというものです。
なお、C#はほぼ初心者で、特に.NETはAPIがほとんど分かりません。MSDNとか検索で調べた結果をまとめたものです。
エントリの最後にコードの最終版を載せています。


やりたいこと。

  • コンソールアプリ
  • コマンドライン引数をパラメータにする(URLエンコード
  • HTTPアクセス
  • JSON解析(静的)

環境

  • Windows 7 Professional 32bit
  • Visual Studio Express 2012 + .NET Framework 4.0
    • VSは.NET 4.0 に対応していればこれより古くてもたぶんOK
    • JSON直列化(後述)は4.0以降

余談ですが、最近Visual Studioに慣れてきて、使うのが楽しくなってきました。特にVS2010以降はいいですね。


Webアプリ仕様(仮)

こんな感じのアプリを想定しています。

  • リクエストURL
http://example.com/app/search?type=json&q=

パラメータqにURLエンコードしたパラメータを付けると、HTTP経由でJSONが返されます。

  • JSONの結果サンプル
{
   "message" : "メッセージ",
   "records" : [
      {
         "id"   : "A0001",
         "name" : "社員1",
         "age"  : 21
      },
      {
         "id"   : "A0002",
         "name" : "社員2",
         "age"  : 23
      }]
}

JSONはネスト(入れ子)になっています。


コンソールアプリ

新しいプロジェクトで「C# コンソール アプリケーション」を選択します。
名前は"JsonTest"とします。
.NETのバージョンが".NET Framework 4.0 client profile"になっている場合は、".NET Framework 4.0"に変更します。


デバッグモードでメッセージを出力させる*1には、System.Diagnotics.Debugクラスを使用します。

using System.Diagnotics; // Debug.WriteLine("");

コマンドライン引数をパラメータに

パラメータqには、ブラウザだったらテキストボックスにスペース区切りで入力するように、コマンドライン引数をスペース区切りしたものを指定します。
URLエンコードには、System.Web.HttpUtilityクラスのUrlEncodeメソッドを使用します。これを使用するには、参照設定に"System.Web"を追加する必要があります。文字列結合には、String.Joinメソッドを使用します。

using System.Web; // 参照設定の追加が必要
// 中略
string q = HttpUtility.UrlEncode(string.Join(" ", args));

HTTPアクセス

HTTP送受信を行うには、System.Net.WebRequestクラスのCreateメソッドGetResponseメソッドを使用します。

using System.Net;
// 中略
var req = WebRequest.Create(url + q);
req.Headers.Add("Accept-Language:ja,en-us;q=0.7,en;q=0.3");
var res = req.GetResponse(); // res = System.Net.WebResponse

この例では、リクエストヘッダの設定も行っています。


JSON解析(静的)

シリアル化*2を使用して、JSONをオブジェクトに変換することができます。
ここでは、静的な取得方法を使います。動的(dynamic型)に取得する方法もありますが、よく知らないので、今回は静的だけでやります。
これらの機能を使用するには、参照設定に"System.Runtime.Serialization"を追加する必要があります。


まず、構造を表す型を定義します。System.Runtime.SerializationのDataContractAttributeクラスの属性"DataContract"と"DataMember"を指定することで、シリアル化可能であることを宣言できます。


前述のJSONにあてはめると、こんな感じになります。

using System.Collections.Generic; // List
using System.Runtime.Serialization; // DataContract,DataMember
// 参照設定の追加が必要

// 中略

[DataContract]
public class ServiceResult
{
    [DataMember]
    public string message { get; set; }
    [DataMember]
    public List<Record> records { get; set; }

    [DataContract]
    public class Record
    {
        [DataMember]
        public string id { get; set; }
        [DataMember]
        public string name { get; set; }
        [DataMember]
        public uint age { get; set; }
    }
}

逆シリアル化(デシリアライズ)するには、次のようにします。その他のシリアル化については、System.Runtime.Serialization 名前空間を参照してみてください。

var resStream = res.GetResponseStream(); // resStream = System.IO.Streamクラス
var serializer = new DataContractJsonSerializer(typeof(ServiceResult));
ServiceResult info = (ServiceResult)serializer.ReadObject(resStream);
resStream.Close();
res.Close();

完成版

ここまでのを合体させて、ちょっとだけブラッシュアップして完成です。
ちょっとごちゃごちゃしますが、出力:デバッグ窓に結果が出力されます。

using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Web;

namespace JsonTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // URLエンコーディング
            string url = "http://example.com/app/search?type=json&q=";
            string q = HttpUtility.UrlEncode(string.Join(" ", args));
            Debug.WriteLine("encoded q: " + q);
            // HTTPアクセス
            var req = WebRequest.Create(url + q);
            req.Headers.Add("Accept-Language:ja,en-us;q=0.7,en;q=0.3");
            var res = req.GetResponse();
            // レスポンス(JSON)をオブジェクトに変換
            ServiceResult info;
            using (res)
            {
                using (var resStream = res.GetResponseStream())
                {
                    var serializer = new DataContractJsonSerializer(typeof(ServiceResult));
                    info = (ServiceResult)serializer.ReadObject(resStream);
                }
            }
            // 結果を出力
            Debug.WriteLine("message: " + info.message);
            Debug.WriteLine("record count: " + info.records.Count);
            foreach (var r in info.records)
                Debug.WriteLine("  id={0}, name={1}, age={2}", r.id, r.name, r.age);
        }
    }

    [DataContract]
    public class ServiceResult
    {
        [DataMember]
        public string message { get; set; }
        [DataMember]
        public List<Record> records { get; set; }

        [DataContract]
        public class Record
        {
            [DataMember]
            public string id { get; set; }
            [DataMember]
            public string name { get; set; }
            [DataMember]
            public uint age { get; set; }
        }
    }
}

*1:デバッグモードでConsole.WriteLineの結果が見られないのは不便ですね。

*2:.NETではこう呼ぶんですね。serialization, シリアライゼーション。Javaだと直列化とも。