technoshop

杉並区和泉のソフトウェアハウス、株式会社テラソフトの技術ブログです。主に.NET MVCとKnockoutJSの情報をまとめます。

.Net MVC4 で KnockoutJS ~【基礎編3】成績表を作ってみよう(Bootstrapでデザインも!)Part1

ザクです。台風18号が来たため午前中は家で仕事してました。

基礎編3回めです。Knockoutの練習に使えるエクセルっぽいものって何かなと考えていました。データが学校関連なので、成績表にしてみました。学生さんには、一肌脱いでもらうことになります。

Bootstrapでテーブルを作る

今の見た目があまりのもダメなので、ここらでBootstrapの出番です。クラス登録日(Enrollment Date)とかどうでもいいですし、MVC4でCRUDもやらないので余計な部分を別のに書き換えます。

<h2>Student Grades</h2>

<table class="table table-bordered">
    <tr>
        <th rowspan="2">
            Last Name
        </th>
        <th rowspan="2">
            First Name
        </th>
        <th rowspan="2">
            Credits
        </th>
        <th colspan="3">
            Grade
        </th>
    </tr>
    <tr>
        <th>
            A
        </th>
        <th>
            B
        </th>
        <th>
            C
        </th>
    </tr>
    <!-- ko foreach: Students -->
    <tr>
        <td><span data-bind="text: LastName"></span></td>
        <td><span data-bind="text: FirstMidName"></span></td>
        <td>38</td>
        <td>1</td>
        <td>3</td>
        <td>1</td>
    </tr>
    <!-- /ko -->
</table>

Index.cshtmlのスクリプト部分はそのままで、HTMLの部分だけ上のように書き換えてみました。tableタグにBootstrapのclassを入れました。

f:id:technoshop:20141003112845j:plain

みちがえるように成績表っぽくなりました!それぞれの生徒さんが今何単位(Credits)取れていて、成績(Grade)ABCがそれぞれ何個あるのか集計するアプリを作ります。エクセルちっくですね。単位は成績がとれたクラスの単位を集計し、個々の成績を数えます。アメリカの大学卒業生の成績確認によく使うGPA(Grade Point Average)なんかも横に出せそうです。

MVC4のモデルでWeb APIで取得するデータを調整する

ビューのひな形ができたので、ここに入れるデータを確認します。EnrollmentとCourseのデータとか見てみます。Advanced Rest ClientでEnrollmentデータを出してみると、GradeはABCの文字列ではなくIntで出てますね。C#Enum使ってるので仕方ありません。0がA、1がB、2がCなので、Knockout側で適当に変換して使います。単位のデータはCourseの方にありますね。Enrollmentで持ってるIdから引っ張ってこれそうです。MVC4側を調整して、Courseのデータも取ってこれるようにしてみましょうか。

Web APIで何のデータを取ってくるかはモデルのファイルに[JsonIgnore]をつけたり外したりするんでしたよね。まずEnrollmentを直します。

    public class Enrollment
    {
        public int EnrollmentId { get; set; }
        public int CourseId { get; set; }
        public int StudentId { get; set; }
        public Grade? Grade { get; set; }

        public virtual Course Course { get; set; }
        [JsonIgnore]
        public virtual Student Student { get; set; }
    }

Courseのナビゲーションプロパティの上にあった[JsonIgnore]を消しました。これでEnrollmentの子(Studnetの孫)データとして、Courseのデータが取れるはずです。次にCourseです。

  public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        [Display(Name = "Number")]
        public int CourseId { get; set; }

        [StringLength(50, MinimumLength = 3)]
        public string Title { get; set; }

        [Range(0, 5)]
        public int Credits { get; set; }

        [Display(Name = "Department")]
        public int DepartmentId { get; set; }

        [JsonIgnore]
        public virtual Department Department { get; set; }
        [JsonIgnore]
        public virtual ICollection<Enrollment> Enrollments { get; set; }
        [JsonIgnore]
        public virtual ICollection<Instructor> Instructors { get; set; }
    }

綺麗に階層化されたJsonデータを作ろうと思うと、こちらではむしろ親テーブルその他へのリファレンスが邪魔になるので[JsonIgnore]をナビゲーションプロパティにつけまくります。とりあえず、これ以上のデータは今のところ不要なので全部に付けました。もう一度、Advanced Rest Clientでデータを見ます。

f:id:technoshop:20141003133011j:plain

これなら使えそうです。

KnockoutのビューモデルをWeb APIのデータに合わせる

成績表のデータの準備はできました。次は表示用にKnockoutのビューモデルを直してやります。


    // Enrollment Model
    function Enrollment(enroll) {
        var self = this;

        self.EnrollmentId = ko.observable(enroll.EnrollmentId);
        self.CourseId = ko.observable(enroll.CourseId);
        self.StudentId = ko.observable(enroll.StudentId);
        self.Grade = ko.observable(enroll.Grade);
    self.Title = ko.observable(enroll.Course.Title);
        self.Credits = ko.observable(enroll.Course.Credits);
    }

    // Student Model
    function Student(student) {
        var self = this;

        self.StudentId = ko.observable(student.StudentId);
        self.LastName = ko.observable(student.LastName);
        self.FirstMidName = ko.observable(student.FirstMidName);
        self.EnrollmentDate = ko.observable(student.EnrollmentDate);

        self.Enrollments = ko.observableArray().ofType(Enrollment);
        self.Enrollments(student.Enrollments);
    }
    function SchoolViewModel() {

SchoolViewModelの上の部分をこのように直してみました。Studnetモデルの中で、self.Enrollmentsをko.observableArrayで作り、ビューモデルからみたStudent同様にEnrollmentのモデルを上に定義しました。中身はほぼMVC4のモデルの中身と同じです。違うのは、Courseモデルが持っているデータもEnrollment側に持たせた部分です。これはこの後でやる単位の合計を計算するのに都合がいいからこうしました。Web API経由でKnockoutのデータをSave(PUTを使う)したりする場合は、これではモデルがMVC4と合わないので使えなかったりするのですが、まだSaveはしませんし、Courseのデータを変えることはなさそうなので、しばらくこのままで行きます。

Courseからのデータの取り方だけざっくり説明しておくと、ここで受け取ったJsonデータ(enrollの中身)は、JavaScriptのチェインの仕組みを使って取ることができます。enrollの中にはCourseのオブジェクトがぶら下がっているのでそれを書いて、Courseオブジェクト内の変数名を指定するだけで値を取ってこれます。(KnockoutのObservableではないのでおしりに括弧は要りません)

これでビューモデルに必要なデータが取れるか見てみます。ブラウザでStudentをリロードしてください。それで、Knockout Context Debuggerで適当な生徒の上で右クリックします。

f:id:technoshop:20141003143501j:plain

生徒Alexanderが受講している3つの授業のオブジェクトが配列として取れていることが確認できました。TitleやCreditsの値もちゃんと入っています。

ko.computedのなかでko.utils.arrayForEachを使いまくる!

やっと、このチュートリアルのテーマ「Knockoutでエクセルちっく」の本題に入ってきました。KnockoutにはJavaScriptの中でObservableArrayを便利に扱うユーティリティがいくつか用意されています。しかし本家のサイトでこの解説がないんですよね、なぜか。代わりにKnockout情報満載な「Knock Me Out」というブログでの解説ページへのリンク貼っておきます。


Utility functions in KnockoutJS - Knock Me Out

こういう所がKnockoutなんだかなぁ~という気がしますが、他の人がちゃんと説明しているだけマシですか。サイトで説明されているko.utils.arrayForEachを使います。これはC#のForEachとほぼ同じことをやります。さっそく生徒さんの取得単位の合計を計算するko.computedを作ってみましょう。

    // Student Model
    function Student(student) {
        var self = this;

        self.StudentId = ko.observable(student.StudentId);
        self.LastName = ko.observable(student.LastName);
        self.FirstMidName = ko.observable(student.FirstMidName);
        self.EnrollmentDate = ko.observable(student.EnrollmentDate);

        self.Enrollments = ko.observableArray().ofType(Enrollment);
        self.Enrollments(student.Enrollments);

        // Calculate Total Credts for what a student passes
        self.totalcredits = ko.computed(function () {
            var total = 0;
            ko.utils.arrayForEach(self.Enrollments(), function (el) {
                var value = el.Credits();
                if (!isNaN(value)) {
                    total += value;
                }
            });
            return total;
        });
    }

生徒が取っている授業のデータをグルグル回して単位の合計を計算するにはStudentモデルの中でやるのが良さそうです。(その生徒の取得単位ですもんね)Observableを定義している下にself.totalcreditsをko.computedで作りました。

中身はごく単純に、Enrollmentの配列をko.utils.arrayForEachで回して、Creditsの値をtotal変数に積算しています。最後にこれをreturnして出力します。もう一度ブラウザをリロードして、デバッガの結果を見てみます。

f:id:technoshop:20141003150227j:plain

アレックス君の取得単位「9」がEnrollmentの配列のデータを使ってちゃんと計算されました。他の生徒も大丈夫そうです。ビューに表示できそうです。

ひとまず中間成績発表

ビューモデルに取得単位のデータが準備できました。これをビューにバインドして表示します。単にテキストとして表示するならtextで良さそうです。

    <!-- ko foreach: Students -->
    <tr>
        <td><span data-bind="text: LastName"></span></td>
        <td><span data-bind="text: FirstMidName"></span></td>
        <td><span data-bind="text: totalcredits"></span></td>
        <td>1</td>
        <td>3</td>
        <td>1</td>
    </tr>
    <!-- /ko -->

HTMLのテーブルで、Studentsのforeachをやっている部分にtotalcreditsを入れました。ブラウザで表示してみます。

f:id:technoshop:20141003151155j:plain

ちょっwww 全体的に成績はよくないです。2名やばい生徒がいますね。長くなったので続きはPart2で。

.Net MVC4 で KnockoutJS ~【基礎編2】 WebAPIで取得したデータをビューに表示(初めてのObservableArray)

ザクです。月曜公開にしようかと思いましたが、週末これで遊んでくれる人がいるかもと思って頑張って仕上げました!

「.Net MVC4 で KnockoutJS」の2回目はObservableArrayです。

今回は.Net MVC4側でWeb APIを使います。GETでモデルのデータを取ってきて、Knockoutのビューモデルに流し込む方法を説明します。それをビューに表示できたら一件落着という感じです。

これまで.Net MVC4で土台になるアプリの準備方法をWeb APIを含めて解説してきました。おさらいが必要な方はこちらからどうぞ。(全6回)

.Net MVC4にBootstrapをLessで組み込む - technoshop
Entity Framework Code First でRailsっぽくDBを管理する - technoshop
Entity Framework Code First でのリレーションシップ - technoshop
One-to-One 再入門(カスケードデリートとかもあるよ) - technoshop
SimpleMembershipをCode Firstに組み込む - technoshop
.Net MVC4 でも Web API で REST してみる! - technoshop

最後の回でWeb APIを使ってDBからデータを取り出せるようになりましたね。データをKnockoutのビューモデルに渡してやる準備をします。

Knockoutビューモデルの作成(含むWeb APIの呼び出しとデータローディング)

Web APIから受け取るデータの入れ物として、KnockoutのビューモデルにMVC4のモデルと同じ構造体を用意してやります。Studentモデルをベースにビューモデルを作ります。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

~ 一旦削除 ~

@section Scripts {
<script type="text/javascript">
(function () {
    // Student Model
    function Student(student) {
        var self = this;

        self.StudentId = ko.observable(student.StudentId);
        self.LastName = ko.observable(student.LastName);
        self.FirstMidName = ko.observable(student.FirstMidName);
        self.EnrollmentDate = ko.observable(student.EnrollmentDate);
    }
    function SchoolViewModel() {
        var self = this;

        self.Students = ko.observableArray().ofType(Student);
        GetSchoolData();
        //Function to call Web API
        function GetSchoolData() {
            //Ajax Call Get 
            $.ajax({
                type: "GET",
                url: "/api/StudentAPI/",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (data) {
                    self.Students(data); //Put the response in ObservableArray
                },
                error: function (error) {
                    alert(error.status + "<--and--> " + error.statusText);
                }
            });
            //Ends Here
        }
    }
    ko.applyBindings(new SchoolViewModel());
})();
</script>
}

Index.cshtmlを上のように書き換えました。いきなり実践的なコードになりましたw

Studentモデル以外にも今後色々入れていくことになるので、親となるビューモデルは「SchoolViewModel」としました。MVC4のStudentモデルを参考に同じ名前のStudentモデルと変数を用意します。扱うモデルが増えてくるとビューモデルの中がゴチャゴチャしてくるので、中で利用するモデルはMVC4のモデルに合わせて分けて定義します。(注:Knockoutのビューモデルでの変数定義の大文字小文字はC#側に合わせてください。JavaScriptがケースセンシティブなのでJsonデータのやり取りで悩まなくてすみます)Studentモデルの変数は全てKnockoutのObservableとして代入しました。MVC4側のStudentモデルにはEnrollmentsコレクションのナビゲーションプロパティがありますが、わかりやすくするためにここでは一旦省きます。

ビューモデルの中は主に(var self = this;は無視して)3つのコードで構成されています。

最初にko.observableArrayが出てきました。これは配列のデータを格納するのに使います。ofType(Student)は、Knockout用の外部ライブラリで、ビューモデルをここで示したように作るだけで、ビューモデルにJsonデータを簡単に流し込めるようにするものです。下にknockout-oftypeのGithubと作者による解説(英語)へのリンクを貼っておきます。

lelandrichardson/knockout-oftype · GitHub
Working with Typed Arrays in Knockout.js - Tech.pro

jsファイルをダウンロードしたら、Scriptsフォルダに加えてください。BundleConfig.csも修正します。

      bundles.Add(new ScriptBundle("~/bundles/js").Include(
                        "~/Scripts/jquery-{version}.js",
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*",
                        "~/Scripts/knockout-{version}.js",
                        "~/Scripts/knockout-array-oftype.js",
                        "~/Scripts/bootstrap.js"));

次にGetSchoolDataです。これは真下にある同名のファンクション呼び出しです。ファンクションの中身はJQueryajaxコールになっています。非同期でStudentAPIをGETで呼びます。呼び出しが成功するとJson形式でStudentsデータを取得し、self.Studentsにデータを流し込んでオブジェクトを作ります。これが完了したら、ビューでStudentのデータが取り出せるようになっているはずです。

Chrome拡張機能を使ったKnockoutのデバッグ

Student/Indexの結果をブラウザで確認します。アウトプットのところはまだ何も書いていないので勿論なにも表示されません。でも、Knockoutはこの状態でもレンダリング用のデータを用意してくれています。

JavaScripのデバッグにはChromeが便利なので、KnockoutもChromeを使ってデバッグします。Knockoutの開発の際に便利な機能拡張を入れましょうか。Chromeのウェブストアで「Knockout」を検索します。でましたね。「Knockoutjs context debugger」というやつがそうです。インストールしましょう。

インストールできましたか?これの使い方は簡単で、Knockoutのビューモデルが仕込まれているhtmlのbodyのどこかを右クリックするだけで、その要素にバインドされているビューモデルの中身が表示されます。バインドしていなくても、bodyから見えるようになっているはずです。

f:id:technoshop:20141002135251p:plain

Studentデータがオブジェクトの配列として取れました!

ObservableArrayをforeachバインディングで表示する

表示するオブジェクトの準備は整いました。早速、ビューにバインドしてみましょう。

ObservableArrayに格納されているデータを一覧表示するには「foreach」バインディングを使います。削除した元のテーブルを戻しつつKnockout用に直してみます。

<h2>Index</h2>

<table>
    <tr>
        <th>
            Last Name
        </th>
        <th>
            First Name
        </th>
        <th>
            Enrollment Date
        </th>
        <th></th>
    </tr>
    <!-- ko foreach: Students -->
    <tr>
        <td><span data-bind="text: LastName"></span></td>
        <td><span data-bind="text: FirstMidName"></span></td>
        <td><span data-bind="text: EnrollmentDate"></span></td>
        <td>
            <a data-bind="attr: { href: '/Student/Edit/' + StudentId() }">Edit</a> |
            <a data-bind="attr: { href: '/Studnet/Details/' + StudentId() }">Details</a> |
            <a data-bind="attr: { href: '/Student/Delete/' + StudentId() }">Delete</a>
        </td>
    </tr>
    <!-- /ko -->
</table>

ビューのHTML側はこんな感じになります。Knockoutを使うとRazorはあまり使わなくなります。もちろん共存させることも可能ですが。

<!-- ko foreach: Students --><!-- /ko -->で囲った部分がforeachバインディングです。ビューモデルのStudentsオブジェクト(ko.observableArrayで中身は個々のStudentオブジェクトの配列を持つ)を指定して、中身を繰り返し表示させます。このforeachの囲いの中では、Studnetオブジェクトの変数名を直接指定して、特定のHTML要素にバインドできるようになります。文字だけ表示させる場合はtextバインディングでtd要素内に各変数の中身を出力し、リンクのhrefの中に表示する場合は、attrバインディングでhrefをJavaScriptでStudentIdをベースになるURLの文字列とコンカチします。

attrバインディングのStudentIdのおしりに「()」が加えられている点に注意してください。Knockoutの変数に格納された値をプログラム的に取り出す時(バインディングに直接指定する時は要らない)は必ず括弧()が必要です。(この括弧はKnockout開発における悩みどころの一つです)KnockoutのObservableはファンクションなので、値を取り出す(returnをもらう)には括弧が必要というわけです。JavaScriptの中でObservableを扱う際にはしょっちゅう出てくるので注意しておいてください。

http://kojs.sukobuto.com/docs/foreach-binding
http://kojs.sukobuto.com/docs/attr-binding

リロードして表示させます。

f:id:technoshop:20141002152846j:plain

今回はここまで。次回は表計算っぽいのやりますよ~。

.Net MVC4 で KnockoutJS ~ 【基礎編1】Knockoutのキホン、ViewModelとBinding

ザクです。やっと始まりましたKnockoutJS講座。タイトルは「.Net MVC4 で KnockoutJS」です。どうぞご贔屓に。

Knockoutの解説によくあるMVVM(Model View ViewModel)パターンの説明ははしょります。あれ、逆によくわかんないですよね。(並び順もなんとなく違う。。。Cの代わりなのでこれでいいのか)ようするにKnockoutを理解するには要らない概念と思われます。代わりに、Knockoutでもっとも重要な「ビューモデルとバインディング」について説明しますね。

Knockoutのアップデート

ビューモデルとかやる前に、Knockoutのアップデートです。MVC4に入っている2.2では古いので最新のにまで上げちゃいます。(執筆時点での最新はv3.2です)

Knockout : Downloads

ダウンロードできたら、プロジェクトのScriptsフォルダに放り込みます。古い方は削除してもいいでしょう。

Knockoutをアプリにバンドル

KnockoutのファイルをScriptsフォルダに入れるだけでは、もちろんアプリで使えませんね。バンドルしてやる必要があります。覚えてますか?App_Startフォルダの中にあるBundleConfig.csでアプリで利用するjs/cssファイルを決めてやるんでしたね。以下のように修正してください。

bundles.Add(new ScriptBundle("~/bundles/js").Include(
                        "~/Scripts/jquery-{version}.js",
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*",
                        "~/Scripts/knockout-{version}.js",
                        "~/Scripts/bootstrap.js"));

上のようにデフォルトのbundlesに突っ込んでしまえばいいと思います。アプリを起動して、ソースを確認してみてください。3.2.0-debugが取り込まれているのが確認できました。debugになってますが、開発中はこのままのほうが都合がいいので、このままにします。コンパイルするときにBundleが良きにはからってくれます。

コントローラとビューにKnockoutを入れてみる

StudentテーブルをKnockoutで表示するためにMVC4のコントローラとビューを用意しましょう。面倒なのでビュー付きでコントローラをスキャフォールドしてしまいます。追加のダイアログボックスはこんな感じになるはずです。

f:id:technoshop:20141001111539j:plain

これで適当なビューファイルが使えるようになりました。Knockoutの練習用にコントローラを調整します。IndexでStudentのリストをMVC4で表示しているのを、代わりにKnockoutで表示するのを当面のゴールとしましょうか。MVC4でDBからモデルのデータを取ってくるところが不要になるので、この部分を消すかコメントアウトし、ビューの呼び出しは中身を空にします。

public ActionResult Index()
{
    return View();
}

コントローラのIndexはこんな感じになります。次に、ビューの方です。こちらは試しにKnockout本家サイトのチュートリアルにあるFirst NameとLast Nameを表示するやつを試しに入れてみます。

learn.knockoutjs.com

あとで、Studentのリストを表示するのに使うので、元のIndex.cshtmlはバックアップとってもいいですね。(Gitならベター)まず、h2以下をバサッと削除し、下のコードに置き換えます。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>

@section Scripts {
<script type="text/javascript">
    function AppViewModel() {
        this.firstName = "ザク";
        this.lastName = "テラソフト";
    }

    // Activates knockout.js
    ko.applyBindings(new AppViewModel());
</script>
}

これだけです。アプリを起動して、Studentコントローラを見てみます。

f:id:technoshop:20141001114626j:plain

名前のところに、KnockoutのAppViewModelで定義してある変数に入れた値が表示されました。Knockoutの基本的な書き方はこれだけです。簡単ですね。KnockoutがAngularJSやBackboneなど他のものに比べて習得しやすいのは、この単純さにあります。開発が進むにつれビューモデルの構成が複雑になったり、ユーティリティ的なコードを加えられたりすることになりますが、基本的には2つのコードで動きます。上のコードを説明します。

Knockoutでは、まずビューモデルを定義します。このサンプルではAppViewModelとなっています。JavaScriptのファンクションになっていれば名前はなんでもいいです。ただ「ViewModel」とつけてやると、「ああここがKnockoutの始まりか」とわかって親切ですね。ビューモデルはKnockoutで使う構造体の定義です。ビューモデルと言っているので、実行時にビューにバインドします。一番最後の行の「ko.applyBindings(new AppViewModel());」はこのViewModelの中身をビューで使えるようにする実行のコードですね。JavaScript側はこれだけです。

ビューファイルの中では、特定のhtmlタグをビューモデル内の変数にバインドするdata-bindを書くことで、ビュー側で変数をどこでどのように表示するか宣言します。(宣言的バインディングです)これだけで、ビューモデルの中の変数をビュー側に持ってきて表示することができるようになりました。

Observableを使ってみよう

Observableを使ってもっとKnockoutらしさを実感してみましょうか。ObservableはKnockoutの変数で、これが双方向バインディングのキモとなります。

Knockout : Observables

ビューモデルの変数にObsrevableを使ってみます。ビューにも2行追加します。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>

@section Scripts {
<script type="text/javascript">
    function AppViewModel() {
        this.firstName = ko.observable("ザク");
        this.lastName = ko.observable("テラソフト");
    }

    // Activates knockout.js
    ko.applyBindings(new AppViewModel());
</script>
}

先ほどビューモデルに定義した名前の変数にko.observableを使って代入しました。これだけで変数が双方向バインディングの変数になります。Studentをリロードして、フォームの名前を変えてみてください。(フォームの値を変えてからイチイチクリックちなければなりませんが)値を変えたら、上のテキストもその値に変わったと思います。ここでは、同じ変数を2つの違う形でビューにバインドしています。表示がtextで、入力がvalueです。JQueryで同じことをやろうと思うと、これ結構面倒くさいです。エクセルアプリみたいに入力箇所が増えてくるとidやclassがゴチャゴチャしてきて管理不能になります。Knockoutではdata-bindをビューに書いてやらなければなりませんが、JQueryに頼ることなくビューモデルの中のデータを操作して簡単にビューに表示できるようになりました。

エクセルの関数ちっくなComputed Observable

3種類あるObservableのうちもう一つComputed Observableも見ておきましょう。Computedというだけに、ビューモデルを構成している変数をいろいろ組み合わせて、より複雑な結果を出力する変数が作れます。

Knockout : Computed Observables

Observableみたいに、ビューモデルに変数を作り、それにko.computedを使って代入してやります。ko.computedの中身はJavaScriptの「ファンクション」になります。下がfirstnameとlastnameをコンカチするko.computedを追加したサンプルです。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>First name: <strong data-bind="text: firstName"></strong></p>
<p>Last name: <strong data-bind="text: lastName"></strong></p>

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>

<p>Full name: <strong data-bind="text: fullName"></strong></p>

@section Scripts {
<script type="text/javascript">
    function AppViewModel() {
        var self = this;

        self.firstName = ko.observable("ザク");
        self.lastName = ko.observable("テラソフト");
        self.fullName = ko.computed(function () {
            return self.firstName() + " " + self.lastName();
        });
    }
    ko.applyBindings(new AppViewModel());
</script>
}

このko.computedでは単純にビューモデルの変数をスペースを間に挟んでくっつけたものをfullnameとして出力しました。ここでは簡単な文字列の操作でしたが、数値を扱えばエクセルちっくなアウトプットを出す特別な変数を作ることができますね!(ザクが「エクセルアプリをウェブに移植するためのライブラリ」と主張する根拠はこれです)

ビューモデルのなかの「var self = this;」って何?と思った人もいると思います。(するどい!)これはビューモデルで構造体を作るとき、ファンクションが入れ子になることがKnockoutではしょっちゅうあるので、親のファンクションにある変数を子のファンクションで取り出したいとき、親側でこの一行を入れておきます。すると子のファンクション側ではself.???として、親の変数が利用できるというわけです。(JavaScriptのスコープの話ですね)Knockoutで構造体を作るときの作法として覚えておきましょう。

今回はここまで。次回はObservableArrayを使ってループ表示をやりますよ~。

.Net MVC4 でも Web API で REST してみる!

ザクです。そろそろMVC4飽きてきたと思います。「はよKnockoutやらんかい」と思われた方も多いと思いますが、物事には順序があります。そういうものです。

これまでは、KnockoutJSのチュートリアルをやるために必要なMVC4側の知識をおおまかに解説してきました。(たいていのアプリのたたき台は、これまで説明してきたスキルを使って作れると思います)今回のWeb APIはKnockoutJSをMVC4アプリケーション上でやる上で最も重要な知識と思われます。しっかり習得しましょう。

Web API のコントローラを作成する

Web APIは、MVC4になって追加された新機能です。これでようやく.NetでもRESTが簡単に実装できるようになりました。Web APIについての説明は詳しくしませんが、知りたい人はこの辺を読むと良いと思われます。

MSDN Blogs

ザクの講座では必要な知識だけ手短にまとめたいので、不要な知識ははしょります。

Web APIで必要なものは「専用コントローラとルーティング」のファイルです。(APIで取り出すモデルが必要なのは言うまでもないでしょう)Web APIだけをやるプロジェクトを作ってもいいのですが、既存のMVC4アプリのプロジェクトに追加する形で加えても構いません。SPAで作るところ以外(例えばユーザー管理とか)はAPI使いませんからね。試しに、これまで作ってきたチュートリアルのプロジェクトで作ってみましょう。

既存のモデルからデータを取り出すAPIを作るならスキャフォールドすると楽ちんです。プロジェクトのControllersフォルダを右クリックして追加を選び、コントローラを新規作成します。ダイアログボックスが開くので以下のように設定します。

f:id:technoshop:20140930102520j:plain

StudentAPIController.csファイルがControllersフォルダ内に作られました。4種類のHTTPメソッドに対応したアクションが作られ、モデル名やコンテクスト名も正しく設定されています。見た感じ良さそうなので、とりあえずビルドしておきます。

Chromeアプリ「Advanced REST Client」でGETを呼んでみる

Web APIからデータを取り出すには、コントローラのアクションの上にコメントされたURLを使ってGETを呼び出せばいいだけですが、View側でこれを呼び出すものが今何もありません。。。どうしたらいいのか?ブラウザでAPIを呼び出して、結果が見れたらAPIがちゃんと応答しているか簡単に確認できますよね。それには、Chromeアプリ「Advanced REST Client」を使うとラクです。こいつをChromeに追加します。

f:id:technoshop:20140930150119j:plain

起動するとこんな画面が出たと思います。一番上がURLを書くところになっているので、自分のアプリのアドレスを入れます。GETにチェックが入っているので、Sendボタンを押します。

あれあれ、エラーが出ました。

f:id:technoshop:20140930150453j:plain

「シリアル化に失敗しました」とあります。Web APIからJsonでデータを取ってくるとき、モデルにつけてるナビゲーションプロパティが悪さをすることがあります。これを無視する設定をGlobal.asaxに追加します。

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
            WebSecurity.InitializeDatabaseConnection(
                "DefaultConnection",
                "UserProfile",
                "UserId",
                "UserName", autoCreateTables: true);

            GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        }

ビルドして、もう一度APIに繋ぎます。まだエラーが出ますね。エラーの下の方を見てみます。「"この Command に関連付けられている DataReader が既に開かれています。このコマンドを最初に閉じる必要があります。"」とありますね。調べたら、これで直るみたいです。Web.configのDefaultConnectionに下を加えてください。

MultipleActiveResultSets=true;

ビルドして、APIに繋ぎます。今度はちゃんとレスポンスが帰ってきました!でも、ナビゲーションプロパティの入れ子がややこしくなっていますね。モデルの定義がちょっと入り組んでるので仕方がないかもしれませんが。Studentからみて、子供のEnrollmentまでが取れれば良いとして、余計なデータを取ってこないように、API取得用にEnrollmentモデルを調整します。ナビゲーションプロパティの上に[JsonIgnore]を加えてみてください。

public class Enrollment
    {
        public int EnrollmentId { get; set; }
        public int CourseId { get; set; }
        public int StudentId { get; set; }
        public Grade? Grade { get; set; }

        [JsonIgnore]
        public virtual Course Course { get; set; }
        [JsonIgnore]
        public virtual Student Student { get; set; }
    }

だいぶスッキリしました!

f:id:technoshop:20140930163552j:plain

そろそろKnockoutJSばどげんかせんと。。。

ザクです。九州の産まれではありません。

でも、KnockoutJSをどげんかせんといかんとおもうちょります。AngularJSの人気がやばいです。日本語の本も出ました。気になってしかたがありません。でも、ザクはKnockoutJS使ってアプリ開発してます。

f:id:technoshop:20140929152918j:plain
Knockout : Home

理由をざっくりあげてくと

  • お客さんの環境ではまだまだIE使っている(普通にIE9とかザラ)
  • 今エクセルでやってる表計算をウェブアプリでやりたいという要件
  • なんでもかんでもJavaScriptでやりたいわけではない(Viewのところだけインタラクティブに)

開発と実行がWindows環境で、作っているのがギョームアプリなので、KnockoutJSでも十分だったりするんですよね。それに、覚えることも少ないです。コントローラありませんし。IEも6まで対応とかしてたりとか。(KnockoutJSの開発者がMicrosoftに勤めてるのが大きいかも)

エクセルっぽいSPA(シングルページアプリケーション)を作るんだったら、KnockoutJSでいんじゃなーいという気がします。

KnockoutJSってなんやろ?

本家サイトを見ると「モデル-ビュー-ビューモデル(MVVM)」パターンだそうです。いみふ。ぶっちゃけ、「エクセルアプリっぽいものをウェブで作るライブラリ」でいいんじゃないですか!

KnockoutJSのキモは、ビューモデルに格納されたデータをビューと紐つける(バインドする)ことです。双方向バインディングというやつです。ビューのフォームからビューモデルのデータをアップデートしたり、JavaScriptでビューモデルのデータを操作したりすることで、それを表示してる部分が自動的に更新したりするということです。わかりやすい例を挙げるならば「エクセルの関数」ですよ。入力用のセルにじゃかじゃか数値を入れていって、ここからここまでの合計(SUM)を出せとかよくやると思いますが、まさにアレなのです。中の数字を変えたら、もちろん合計も変わります。こう考えると、KnockoutJSの使い道も考えやすくないですか?

KnockoutJSといったらObservable

はい、反論ありません。KnockoutJSは全てこれの作り方、使い方次第です。3種類あったりしますね。

  • Obserbable:監視する変数に使います。変数の値が一つで良い時ですね。MVC4側のモデルからデータを引っ張ってきて、モデルのプロパティと同名のObserbableをビューモデルで定義してやって、そこに放り込むことが多いです。
  • ObserbableArray:これは配列になったObservableです。配列なので、モデル丸ごととか複雑な構造体を放り込めます。モデルのプロパティの一個一個は上に書いたとおりObservableにすることが多いです。配列なので、ループでビューに出力したりしますね。それようのテンプレートの仕組みも用意されています。
  • Computed Observable:Observable同士を組み合わせて、より複雑なアウトプットをするやつと考えればいいでしょうか。さっきのSUMなんかはこれを使ってJavaScriptゴリゴリ書いて作ります。一度これを作ってしまうと、この中で使っているObservableの値を変えることで、ここのアウトプットも変えることができます。

KnockoutJSってテスタブルなん?

Yes. ただ、KnockoutJS自体はビューモデルとビュー間の双方向バインディングやるためだけのライブラリだったりするので、テストには別のライブラリを使います。ザクが使ってるのは、Jasmine、RequireJS、Squire.jsですね。テスト自体はJasmineで書いて、ランナーはJasmineそのままとか、Visual Studio2012用のChutzpahなんかも使いますね。フレームワークになってないので、テスト環境は自分で作りこむ必要がありますが、自分好みに環境が作れるのもメリットだったりします。しかし、KnockoutJSのテスト環境に関する情報の少ないこと少ないこと。。。このブログでは、KnockoutJSのテスト方法も詳しく書きますですよ。

数少ない日本語で書かれた実践的KnockoutJSチュートリアルを目指して

KnockoutJSのイントロダクションはこんな感じです。KnockoutJSの実践的な使い方解説をこのサイトで連載するので、ちょくちょく覗いてみてください!(ただいまチュートリアルのアプリ何にするか検討中)