本格的に ASP.NET MVC4 でウェブアプリケーション開発

きちんと @IT の記事とか外人さんのブログとかマイクロソフトチュートリアルとか真面目にやるのが近道なのは解ってるんやけど、やっぱプログラマたるものなんかしら動かしたいやん?

つーわけで、座学はほどほどに ASP.NET MVC 4 で習作を作りながら、その技術を学んで行く。
どこから手をつけて良いか解らんので、チュートリアル的に順番に行くって手順より、要素技術をそれぞれやっつけてって最後に合体させたら無敵のパワーって感じになるようにしてみる。

なので、一番つまらんモデルの要素技術から入る。Entity Framework ってのがブランド名で Ver4 でコードファースト、いわゆる POJO + アノテーションベースの OR マッパーの C# 版。

定義とか理屈の話はともかくとして、Entity Framework 4 のコードファーストの感じは

↓優しい外人さんのコードを何度か写経して、まずまずつかんできた。
Code-First Development with Entity Framework 4 - ScottGu's Blog
http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx

やらなアカンことは大きく2つ。

  • アプリ.Models 名前空間に Entity(POCO)を作る
  • DbContext から派生したデータモデルのクラスを作る

こんだけ。
実際にはデータベースのコネクション設定がよく解ってないんやけど、設定間違ってたりすると組み込みの SQLEXPRESS に接続しちゃうみたい。ほんとはファイルベースの SQL Server CE4 ってのにしたいんやけど、そんなとこで調査時間取られるのも嫌なんで、無視して進む。

なんか動くもん作らなおもんないんで Ajax 使わずに submit で行って来い形式のオレオレツイッターを作ってみる。
何は無くとも ASP.NET MVC4 のプロジェクトを作成する。


次に、Tweet クラス(POCO)を追加する。要するにエンティティですな。


Tweet.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MyTwitter.Models
{
    public class Tweet
    {
    }
}

つぶやきの内容と、作成時間を持たせる。ほんで、ほんまに要るんかどうかワカランけどオブジェクトIDとして機能する筈のサロゲートキーを作る。これは、命名規則が決まっててエンティティ名 + ID って名前だとオブジェクトID(RDB 的にはプライマリーキー)になるらしい。
この辺はトレンド通りに CoC になってるのな。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MyTwitter.Models
{
    public class Tweet
    {
        public long TweetID { get; set; }
        public string Body { get; set; }
        public DateTime DatePosted { get; set; }
    }
}

POCO なのに、デフォルトの using が全部いるのかどうかは不明。
続いて、DbContext から派生したデータモデルクラス。ここでは List 相当の単純なデータモデル。
命名規則があんまり解んないんで、MyDbContext とかにしとく。
MyDbContext.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MyTwitter.Models
{
    public class MyDbContext
    {
    }
}

DbContext クラスを継承するために using の追加が必要

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace MyTwitter.Models
{
    public class MyDbContext : DbContext
    {
    }
}

意味はあんまり解ってないけど、写経の結果↓こういう風に書くことを知った。

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace MyTwitter.Models
{
    public class MyDbContext : DbContext
    {
        public DbSet<Tweet> Tweets { get; set; }
    }
}

これでモデルは完成。初期データもなんも無いんで、ともかくつぶやく UI を準備しよう。
入力欄1つと「つぶやき」ボタンが1つ。こんなの屁の河童かと思ったら意外に難しいのな。(我流じゃワカランかったっつーか)

お作法通りかどうかは置いといて、こんな感じかな?ってので追っかけてみる。
まずは、Views/Home の中にある Index.shtml の中身をざっくり消して POST でのサブミットフォームを作る。
(REST で新規作成は POST メソッド)
Index.shtml

<form method="post" action="/">
    <input type="text" />
    <input type="submit" value="つぶやく" />
</form>

実行結果は↓こんな感じ

※Index.shtml を抱く共通のテンプレートレイアウトがあるんで、ちょっとゴテゴテしてる。

この状態で submit ボタンどんだけ押しても変化が無い。POST メソッドで / って URI へのリクエストを処理せなアカンっぽい。

HomeController クラスに POST メソッドでリクエストされるプレースホルダを追加する。

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "ASP.NET MVC アプリケーションを簡単に始めるには、このテンプレートを変更してください。";

            return View();
        }

        [HttpPost]
        public ActionResult Index(Tweet tweet)
        {
            return View(); //実装は無い。リクエストのルーティングを確認したいだけ。
        }

ルーティングの仕組み解ってないけど、/ が Index() ってコントローラのメソッドにマップされてるんは既存のコードを見りゃ推測できる。GET と POST の振り分けはアノテーションで。@ じゃ無いのはちょっと違和感あり。(笑)

ここで1点注意があって、アノテーションはどこまで行ってもソースコードのメタ情報なんで言語仕様を乗り越えるわけじゃ無い。

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "ASP.NET MVC アプリケーションを簡単に始めるには、このテンプレートを変更してください。";

            return View();
        }

        [HttpPost]
        public ActionResult Index()
        {
            return View();
        }

だから↑こう書くとコンパイルエラーなのな。C++ の時代から変わらない、引数が同じで名前が同じメソッドはオーバーロード出来ない。(コンパイラが判別出来ないんでね)
よくわかんないけど、オーバーロード出来ない奴をどうしても定義したい場合はルーティングでかわせば良いんじゃないかと思う。POST メソッドで / は Index2() にマッピングするとかね。メソッドシグネチャで頑張る的な。

実際にここに来るかどうか検証したいって話なので、ブレークポインタ設定してゴーしてみる。

あっさり、来た!この辺はマイクロソフト!って感じやな。
当然のごとく、引数の tweet には HTML 上の form で入力された値はマップされてないけど。
input タグの name か id 属性でマッピングするんやろけど、この辺は Razor の文法通りやるべきだわな。

プロジェクトテンプレートで生成される初期コードをサンプル代わりに見てみると

<h2>ローカル アカウントを使用してログインします。</h2>
@using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>ログイン フォーム</legend>
        <ol>
            <li>
                @Html.LabelFor(m => m.UserName)
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </li>

↑こんな感じになってる。なかなか複雑じゃないか!無理矢理想像すると m = tweet でOKっぽいかな?
・・・ちゃうかった。

かなり、試行錯誤して結局 @IT とかチラ見して最終的なコードは↓こんな感じに
Index.cshtml

@model MyTwitter.Models.Tweet

@using (Html.BeginForm(
  "Index", // アクション名
  "Home", // コントローラ名
  FormMethod.Post, // HTTPメソッド(method属性)
  new { enctype = "multipart/form-data"} // そのほかの属性
)) {
    @Html.TextBoxFor(
        model => model.Body, // プロパティ
        new { size = 20, maxlength = 140 } // そのほかの属性
    )
    <input type="submit" value="つぶやく" />
}

先頭の @model ってメタ情報設定せなアカンのがワカランかって時間使っちゃった。
この時点で行って来い成功してるんで、POST Index() のメソッド内でデータベースに書き込んで、GET Index() メソッドにリダイレクトして一覧更新する感じでOKの筈。

ビューにレンダリングするためのモデルを渡すのに @model ってアノテーションがあるんやけど、ちょっと思ってたんと違う仕様やったみたい。Play Framework みたいにキーバリューの形でなんでもかんでも放り込めたら良かったんやけど、現時点での自分の理解では、@model は1つしか設定出来ないような気がする。

それならそれで、つぶやく用のページとつぶやきを一覧で見るページを分けたらエエって話なんやけど、ほんまにそういう仕様なのかしらね。

とりあえず、2つつぶやいてそれを結果表示したところ。

時間切れ。今日はここまで。

今日なんとか頑張って大体終わらせて、次は KnockOut.js 使って MVVM パターンで試そうかと思ったんやけど、もうちょっとだけ submit で行ってこいの奴を調べる。
Play Framework によく似てる(つまりは RoR に似てるってことか)って感触が大部分なんやけど、微妙に細かいところが個性的っつーかクセがある気がするのな。

その辺のアーキテクチャっつーよりカルチャーの違いを皮膚感覚で理解するにはもうちょっと時間がかかると思う。
でも、ASP.NET MVC4 悪くない。かなりの生産性を発揮するんじゃ無いかなあ。