2012年11月30日金曜日

[C#] async, await

メソッドの宣言に async を付けると非同期メソッドとなり、メソッドの中で
awaitが指定できるようになる。
await を指定すると非同期メソッドの実行が終わるまで待ってから制御を戻す。

分かりやすい参考サイト
http://ufcpp.net/study/csharp/sp5_async.html#async


HttpClientクラスを使って、asyncとawaitを試してみる。

■どこからかコピペしてきたりしてつくった、おれおれサンプル

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("読み込みを開始します");

            // 非同期で実行
            readWebAsync("http://www.google.co.jp/").ContinueWith(
                (task) =>
                {
                    // task.Result に読み込んだ文字列が入ってる
                    Console.WriteLine(task.Result.Count() + "文字読み込みました。");
                });

            Console.WriteLine("読み込みを待っています");
            Console.ReadKey();
        }

        private static async Task<string> readWebAsync(string url)
        {
            HttpClient client = new HttpClient();
            var res = await client.GetStringAsync(url);
            Console.WriteLine("読み込み完了!");
            return res;
        }
    }



■実行結果
読み込みを開始します
読み込みを待っています
読み込み完了!
46568文字読み込みました。



readWebAsyncメソッドを変えて、応答ステータスコードを取得してみる

        private static async Task<string> readWebAsync(string url)
        {
            HttpClient client = new HttpClient();

            var res = await client.GetAsync(url);

            Console.WriteLine("リクエスト送信&レスポンス受け取り完了!");

            // 成功ステータスコードがチェックする
            res.EnsureSuccessStatusCode();

            Console.WriteLine("ステータスコード:" + res.StatusCode);

            Console.WriteLine("リクエスト内容:" + res.RequestMessage);

            var result = await res.Content.ReadAsStringAsync();

            Console.WriteLine("読み込み完了!");

            return result;
        }



■変更後の実行結果
読み込みを開始します
読み込みを待っています
リクエスト送信&レスポンス受け取り完了!
ステータスコード:OK
リクエスト内容:Method: GET, RequestUri: 'http://www.google.co.jp/', Version: 1.1, Content: <null>, Headers:
{
}
読み込み完了!
46648文字読み込みました。



どうでもいいけど Mainメソッドに async をつけようとしたらコンパイル時に
「'async'修飾子を指定できないエントリポイント」と怒られた(´・ω・`)


ついでにHttpClientについての参考ブログ
http://shiba-yan.hatenablog.jp/entry/20120822/1345563275

2012年11月15日木曜日

[ASP.NET MVC] JSONを受け取る、JSONを返す


2012/12/21 コードが間違っていたので修正 orz


ASP.NET MVC3以上ならJSONをモデルバインディングしてくれるようだったのでやってみる

参考サイト:JavaScriptおよびAJAXの改善点(下の方)
http://www.atmarkit.co.jp/fdotnet/scottgublog/20100811aspnetmvc3/aspnetmvc3.html


適当にJSONを送りつける画面を作った


■ビュー(の一部)

2012/12/21 赤文字の部分を変更
変更前はJSONじゃなくてフォームデータ形式でデータを送信していた模様…

<input type="button" value="JSON送信" onclick="json_post();" />

<script type="text/javascript">
    function json_post() {
        var url = '@Url.Action("JSONTest")';

        var post_data =
            {
                Grade : 1
                , Class : 2
                , Name: "クライアントさん"
                , Results: [
                    { Subject: "数学", Score: 64 }
                    , { Subject: "理科", Score: 70 }
                    , { Subject: "英語", Score: 80 }
                ]
            };


        //var post_data =
        //    {
        //        "Grade": "1"
        //        , "Class": "2"
        //        , "Name": "クライアントさん"
        //        , "Results[0].Subject": "数学"
        //        , "Results[0].Score": 90
        //        , "Results[1].Subject": "理科"
        //        , "Results[1].Score": 88
        //        , "Results[2].Subject": "英語"
        //        , "Results[2].Score": 51
        //    };


        $.ajax(
            {
                url: url,
                type: 'POST',
                data: JSON.stringify(post_data),   // JSONの文字列に変換
                dataType: 'json' 
                contentType:'application/json',    // content-typeをJSONに指定する
                error: function () { },
                complete: function (data) { alert(data.responseText) },
            }
        );
    }
</script>


こんな画面
















ボタンをぽちっと押すと下記のアクションにJSONを送りつける


■コントローラー(の一部)

        [HttpPost]
        public ActionResult JSONTest(SchoolViewModel viewModel)
        {
            // クライアントからJSONを受け取って出力ウィンドウに出してみる
            System.Diagnostics.Trace.WriteLine(viewModel.Grade + "年");
            System.Diagnostics.Trace.WriteLine(viewModel.Class + "組");
            System.Diagnostics.Trace.WriteLine(viewModel.Name);
            foreach (var result in viewModel.Results)
            {
                System.Diagnostics.Trace.WriteLine(result.Subject + ":" + result.Score + "点");
            }

            // クライアントに適当なJSONデータを返す
            var res = new SchoolViewModel()
            {
                Grade = 2
                ,
                Class = 3
                ,
                Name = "サーバーさん"
                ,
                Results = new List<TestResult>() {
                    new TestResult() { Subject = "数学", Score = 64 }
                    , new TestResult() { Subject = "理科", Score = 70 }
                    , new TestResult() { Subject = "英語", Score = 80 }
                }
            };
            return Json(res);
        }

バインドするクラスはこんな感じで何の変哲もないViewModel的なクラス。

    public class SchoolViewModel
    {
        public int Grade { get; set; }
        public int Class { get; set; }
        public string Name { get; set; }
        public List<TestResult> Results { get; set; }
    }

    public class TestResult
    {
        public string Subject { get; set; }
        public int Score { get; set; }
    }

実行した結果


■クライアントから送信したJSONの内容をVisualStudioの出力ウィンドウに出してみたもの
1年
2組
クライアントさん
数学:90点
理科:88点
英語:51点


■サーバーから返されたJSON








やったね!ちゃんと受け取れてるし、JSON返してるし!



↓2012/12/21 削除 そもそもJSONとして送ってなかった。ううう…ごめんなさい。

JSONをコレクションにモデルバインディングする時の注意


最初、コレクションに対応する、クライアントから送信するJSONは、

"Results": [ { "Subject[0]": "数学", "Score": 95 }, { "Subject": "英語", "Score": 51 } ]

のように配列で書いていたのだが、これではListにバインドしてくれなかった。


下記のサイトによると
http://msdn.microsoft.com/ja-jp/magazine/hh781022.aspx

> 少々直観に反することですが、JSON 要求にも同じ要件があります。つまり、JSON 要求もフォーム ポストの名前付け構文に従っている必要があります。
> 既定の値プロバイダーやモデル バインダーでは、データを JSON のフォーム ポストとして表す必要があります。

>    {
>      "UnitPrice[0].Code": "USD",
>      "UnitPrice[0].Amount": 100.00,
>    
>      "UnitPrice[1].Code": "EUR",
>      "UnitPrice[1].Amount": 73.64
>    }


どうやら配列のまま送れないらしい('A`)
フォームデータのように番号をつけないといけないようだ。
そのくせサーバーから返るJSONはしっかり配列になっているので、なんだか納得いかないのだった。



モデルバインディングしなくてもResponse.InputStreamから自力でデータを取り出して
DataContractJsonSerializerでデシリアライズすればクラスに落とし込めるよね!
と思ったけどやはり面倒そうである(なんだそりゃ)

2012年11月4日日曜日

knockout.js

javascriptコードを書きながらknockout.jsを学べる素敵サイト
http://learn.knockoutjs.com/#/?tutorial=intro

まだコレクションのところまでしかやってないけど。

プログラムの仕様書って…


プログラムの構文はネットで検索すればかんたんに答えが見つかるけど
仕様書の書き方については決まった答えがないので難しい。
作るプログラムの種類・規模とか、誰に見せるのか、
とかで、必要になる仕様書がまったく変わってくると思う。

仕様書とはどんなものか?
http://japanese.joelonsoftware.com/PainlessSpecs/2.html

正しい、まともな仕様書って何よ(何)

2012年10月28日日曜日

[C#] Func デリゲート

ラムダ式を書くときにインテリセンスで見かけるFunc型。
C#で定義済みのデリゲートらしい。

■そもそもデリゲートの定義
http://ufcpp.net/study/csharp/sp_delegate.html#definition

■Funcデリゲート
http://msdn.microsoft.com/ja-jp/library/vstudio/bb549151.aspx


Func<T, TResult>

っていう型の引数があったら

「T型の引数を取ってTResult型の戻り値を返すデリゲート」

を渡せばよい事になる。なんのこっちゃ。


Funcデリゲートを引数に取るメソッドを作ってみる。


        public void Test()
        {
            // ラムダ式で匿名メソッドのデリゲートを渡す。

            // (ここではmsg を引数に取り、それに文字列を足したものを戻り値とする)
            string result1 = funcTest(msg => { return msg + "1"; });
            string result2 =
funcTest(msg => { return msg + "2"; });

            Console.WriteLine(result1);
            Console.WriteLine(result2);
        }

        private string
funcTest(Func<string, string> method)
        {
            return method("ほげほげ");
        }




■実行結果
ほげほげ1
ほげほげ2


なんというか、ぴんとこない。まあいいや。


Func(T1, T2, TResult)だとかFunc(T1, T2, T3, TResult)だとか
引数の数を増やしただけのオーバーロードがたくさんあるようだ。



■よく見かける(ような気がする)定義済みのデリゲート、メモ

Predicate<in T>
T型の引数を取ってboolを返す
http://msdn.microsoft.com/ja-jp/library/vstudio/bfcke1bz.aspx

Action<in T>
T型の引数を取って戻り値を返さない
http://msdn.microsoft.com/ja-jp/library/vstudio/018hxwa8.aspx

VsVim

http://visualstudiogallery.msdn.microsoft.com/59ca71b3-a4a3-46ca-8fe1-0e90e3f79329

VisualStudioにVsVimを入れる → VIでコーディングがはかどる → 最強

※効果には個人差があります

2012年10月20日土曜日

[ASP.NET MVC] WebGrid のページング(2)

参考サイト
http://msdn.microsoft.com/ja-jp/magazine/hh288075.aspx

サーバー側のページ切り替えをやってみるテスト。
表示に必要なデータだけをDBから取得する。そしてWebGridでページングする。

■サーバー側のデータ取得(コントローラー)

        /// <summary>
        /// ページングのテスト
        /// </summary>
        /// <returns></returns>
        public ActionResult Paging(int? page)
        {

            // page は WebGrid が生成するページングリンクのURLに
            // GETパラメータとして指定されている
            return View( new PaginatedList<SchoolViewModel>( 

                              getData() , (page != null ? (int)page - 1: 0) , 3 )
            );
        }

        /// <summary>
        /// データを取得します
        /// </summary>
        /// <returns></returns>
        private IQueryable<SchoolViewModel> getData()
        {
            Models.hogeEntities db = new Models.hogeEntities();
            return
                (from sc in db.SchoolClass
                 join s in db.Schooler
                 on sc.ClassID equals s.ClassID
                 orderby sc.Grade, sc.Class
                 select new SchoolViewModel()
                 {
                     Grade = sc.Grade
                    ,
                     Class = sc.Class
                    ,
                     name = s.Name
                 });
        }



ここ↓のPaginatedListというクラスをそのまま流用している
http://www.atmarkit.co.jp/fdotnet/scottgublog/nerddinner/part08.html

■PaginatedList

    public class PaginatedList<T> : List<T>
    {

        public int PageIndex { get; private set; }
        public int PageSize { get; private set; }
        public int TotalCount { get; private set; }
        public int TotalPages { get; private set; }

        public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize)
        {
            PageIndex = pageIndex;
            PageSize = pageSize;
            TotalCount = source.Count();
            TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);

            this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
        }

        public bool HasPreviousPage
        {
            get
            {
                return (PageIndex > 0);
            }
        }

        public bool HasNextPage
        {
            get
            {
                return (PageIndex + 1 < TotalPages);
            }
        }
    }



コンストラクタでIQueryableのデータをSkip&Takeして、PageIndexに該当するデータを取得してる。



■chtml

@model MvcApplication1.Models.PaginatedList<MvcApplication1.Models.SchoolViewModel>

@{
    ViewBag.Title = "Paging";
    Layout = "~/Views/Shared/_Layout.cshtml";

    // WebGridオブジェクト生成
    var grid = new WebGrid(null, rowsPerPage: Model.PageSize);
    grid.Bind(Model, rowCount: Model.TotalCount, autoSortAndPage: false);
}

<h2>Paging</h2>

@* WebGridオブジェクトでレンダリング *@
@grid.GetHtml(columns:grid.Columns(
grid.Column("Class","クラス", canSort:false)
    ,grid.Column("Grade", "学年", canSort:false)
    ,grid.Column("Name", "名前", canSort:false)
    )
)


ポイントっぽいところ
・WebGridのコンストラクタで source にnullを指定する
・Bindメソッドでページの表示に必要なデータと全体の件数を渡し、autoSortAndPage を false にする
(autoSortAndPage を false にするとデフォルトのソート機能も無効になる)






WebGridにページに表示する分のデータしか渡していないけど
ちゃんとページングのリンクを生成してくれている。