ASP.NET Web APIknockout.jsによるSingle Page Application開発 (2)

前回Knockout.js を使用して ブラウザだけで動作するTodoアプリケーションを作成しました。

今回はサーバー側の処理を実装し、前回作成したフロントエンドと結合します。



Web API の作成

以前 取り上げた ASP.NET Web API の開発手順と全く同じです。



(1) EntityFrameworkを追加



(2) Modelクラスの追加

Todo.cs

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

namespace TodoApi.Models
{
    public class Todo
    {
        public int id { get; set; }

        [Required]
        public string summary { get; set; }

        public string detail { get; set; }

        public DateTime limit { get; set; }

        public bool done { get; set; }
    }
}

TodoesContext.cs

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

namespace TodoApi.Models
{
    public class TodoesContext : DbContext
    {
        public DbSet<Todo> Todoes { get; set; }
    }
}

ここまで作成したら、一旦ソリューション全体をビルドしてください。



(3) Controllerクラスの追加

以下の様なメソッドが定義されます。

操作 URL Method 説明
Create /api/Todoes/ post 1件作成
Read /api/Todoes/{id} get 指定したTodoを取得
ReadAll /api/Todoes/ get 全てのTodoを取得
Update /api/Todoes/{id} put 指定したTodoを更新
Delete /api/Todoes/{id} delete 指定したTodoを削除


これで、サーバーサイドの実装は完了しました。



フロントエンドとの結合

今回作成したサーバーサイドの Web API と 前回 作成した フロントエンドをくっつけます。


(1) 初期表示時にTodoリストを表示する

画面の読み込み完了後、非同期にて、GET メソッドで /api/Todoes/ に問い合わせを行います。

サーバーから取得したTodoのリストを observableArray にセットします。

Scripts/app.js

// app.js

/**
 * Todo Model
 * @param 初期値 {object}
 */
var ToDoModel = function (params) { ... };

/**
 * modal dialog model
 */
var DialogModel = function () { ... };

/**
 * ViewModel
 */
var AppViewModel = function () {
    var self = this;

    /* ~~ 省略 ~~ */

    /**
     * Todoをすべて取得する
     */
    self.loadList = function () {
        $.ajax({
            url: "/api/Todoes",
            dataType: "json",
            method: "GET"
        })
            .done(function (data) {
                if (data) {
                    // 一旦全削除
                    self.todoList.removeAll();

                    // 取得したTodoをリストにセット
                    $.each(data, function (index, item) {
                        // yyyy-mm-ddThh:mm:ss という形式で送られてくるので、
                        // 不要な "T" 以降をカット
                        item.limit = item.limit.split("T")[0];
                        if (item.limit == "1960-01-01") {
                            // ダミーの日付の場合はブランクに置き換える
                            item.limit = "";
                        }
                        self.todoList.push(new ToDoModel(item));
                    });
                }
            });
    };
};

// エントリポイント
$(function () {
    var app = new AppViewModel();
    ko.applyBindings(app);

    app.loadList();
});

日付の処理に少し工夫が必要です。

サーバーから送られてくる値は yyyy-MM-ddTHH:mm:ss という形式になっています。 今回は日付のみが必要なので、 不要な部分をカットして表示しています。



(2) 新しいTodoを追加する / Todoの更新を行う

追加の場合、登録ボタンクリック時に 非同期にて POST メソッドで /api/Todoes/ に問い合わせを行います。

更新の場合、登録ボタンクリック時に 非同期にて PUT メソッドで /api/Todoes/{id} に問い合わせを行います。

登録に成功したら、フォームを非表示にして Todoリストを再取得します。

/**
 * 登録ボタンのクリック
 */
self.registTodo = function () {
    var item = self.selectedItem();

    // limitが空だとサーバーサイドでエラーとなるため
    // ダミーの日付を登録する
    var model = ko.toJS(item);
    if (model.limit == "") {
        model.limit = "1960-01-01";
    }

    // ajaxのパラメータ
    var params = {
        dataType: "json",
        data: model
    };

    if (item.id() == 0) {
        // Create
        params.url = "/api/Todoes";
        params.method = "POST";
    } else {
        // Update
        params.url = "/api/Todoes/" + item.id();
        params.method = "PUT";
    }

    $.ajax(params)
        .done(function (data) {
            // 編集フォームを閉じる
            self.selectedItem(null);

            // Todoリストを再読み込み
            self.loadList();

            self.dialog.show({
                title: '登録完了',
                message: '登録が完了しました。'
            });
        });
};


jQueryajax メソッドで、入力されたデータを POST/PUT します。

TodoModel には observable のプロパティがいくつかありますが、 これは Knockout.js が使用する付加情報が含まれています。

これらの情報はサーバー側で登録処理を行うのに邪魔になるため、 ko.toJS メソッドを使用して 純粋な JavaScriptのオブジェクト に変換します。

JSON データの読み込みと保存

また、limit が未指定の場合、サーバーサイドで DateTime に変換する際に エラーとなってしまいます。

空の場合はすごい古い年月をダミー値として登録しておき 表示時にダミー値と一致する場合はブランクに置き換えるようにしました。


function addItem, function updateItem はもう使用しないので、削除しておきます。



(3) リストからTodoを選択すると、詳細を表示する

できるだけ新しい情報を表示するため、リストを選択すると 非同期にて GET メソッドで /api/Todoes/{id} に問い合わせを行うようにします。

取得したデータを入力フォームに表示します。

/**
 * リストからTodoを選択する
 * @param item {ToDoModel} 選択された項目
 */
self.selectTodo = function (item) {
    $.ajax({
        url: "/api/Todoes/" + item.id(),
        dataType: "json",
        method: "GET"
    })
        .done(function (data) {
            if (data) {
                // yyyy-mm-ddThh:mm:ss という形式で送られてくるので、
                // 不要な "T" 以降をカット
                data.limit = data.limit.split("T")[0];
                if (data.limit == "1960-01-01") {
                    // ダミーの日付の場合はブランクに置き換える
                    data.limit = "";
                }
                self.selectedItem(new ToDoModel(data));
            }
        });
};



(4) Todoの削除を行う

削除ボタンクリックで 非同期にて DELETE メソッドで /api/Todoes/{id} に問い合わせを行うようにします。

削除完了後、リストを再読み込みします。

/**
 * 削除ボタンのクリック
 */
self.deleteTodo = function () {
    $.ajax({
        url: "/api/Todoes/" + self.selectedItem().id(),
        dataType: "json",
        method: "DELETE"
    })
        .done(function (data) {
            // 編集フォームを閉じる
            self.selectedItem(null);

            // リストを再読み込み
            self.loadList();

            self.dialog.show({
                title: '削除完了',
                message: '削除が完了しました。'
            });
        });
};



デバッグ実行して、以下を確認してください。




ASP.NET Web APIKnockout.js による Single Page Application開発 について解説しました。

ASP.NET と直接は関係しないために取り上げませんでしたが 実際のアプリケーションには以下の様な機能が必要になってきます。


Single Page Application では Ajax による非同期処理を駆使することで UI/UX の向上が見込まれますが、以下の様な問題点もあります。

非常に進化のスピードが早い分野なので、 実際に開発する際には新しい情報を収集するように心がけてください。