normalian blog

Let's talk about Microsoft Azure, ASP.NET and Java!

Knockout MVC を使ってみる

今回は Knockout.js を ASP.NET MVC で活用するためのライブラリである Knockout MVC を利用してみる。同ライブラリを利用することで、ASP.NET MVC でのモデルバインディングが容易になり、ASP.NET 側と JavaScript 側の機能連携が非常に容易になる。

インストール方法

Visual Studio から NuGet の管理コンソールを起動し、検索窓に「knockout」と入力した結果の P.3 辺りに Knockout MVC が発見できる。
f:id:waritohutsu:20140113134146j:plain

次に、Knockout.js を利用するためには ASP.NET MVC プロジェクトに対して以下の修正をする必要がある。

  • App_Start/BundleConfig
public class BundleConfig
{
    // バンドルの詳細については、http://go.microsoft.com/fwlink/?LinkId=301862 を参照してください
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/jquery.validate*"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryunobtrusive").Include(
        "~/Scripts/jquery.validate.unobtrusive.js"));

        //knockout.s 用
        bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
        "~/Scripts/knockout-{version}.js"));
        bundles.Add(new ScriptBundle("~/bundles/knockoutmapping").Include(
        "~/Scripts/knockout.mapping-latest.js"));

        // 以下は省略
    }
}
  • Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
//中略
</head>
<body>
//中略
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - マイ ASP.NET アプリケーション</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/knockout")
    @Scripts.Render("~/bundles/knockoutmapping")
    @RenderSection("scripts", required: false)
</body>
</html>

という感じで、Knockout.js を画面側で読みこめるように設定しておく必要がある。

簡単な使い方

以下にサンプルコードを記載してみる。

  • Person.cs Name, Age プロパティはさておき、Summary プロパティに Computed 属性を付与している点に注目していただきたい。Computed 属性を付与することで、JavaScript 側にロジックを自動生成することができる。
using System.ComponentModel.DataAnnotations;
using DelegateDecompiler;

namespace KnockoutMVCSample.Models
{
    public class Person
    {
        [Display(Name = "名前"), Required]
        public string Name { get; set; }
        [Required, Display(Name = "年齢"), Range(typeof(int), "0", "200")]
        public int Age { get; set; }

        [Computed]
        public string Summary
        {
            get
            {
                var ret = Name + "は";
                if (Age < 20)
                {
                    ret += "まだまだ未成熟な果実(/ω\)イヤン";
                }
                else
                {
                    ret += "熟れた果実(*´Д`)ハァハァ";
                }
                return ret;
            }
        }

        // 後述するが、 ToString を上書きすると死ぬ
        //public override string ToString()
        //{
        //    return string.Format("私は {0}、{1}歳☆(ゝω・)vキャピ", this.Name, this.Age);
        //}
    }
}
  • HomeController.cs 通常の ASP.NET MVC のコントローラであり、特に留意する点はない
using System.Web.Mvc;
using KnockoutMVCSample.Models;
using System.Diagnostics;

namespace KnockoutMVCSample.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(new Person()
            {
                Name = "幼女",
                Age = 9
            });
        }

        [HttpPost]
        public ActionResult Index(Person person)
        {
            Trace.WriteLine(string.Format("私は {0}、{1}歳☆(ゝω・)vキャピ", person.Name, person.Age));
            return View(person);
        }

        //中略
    }
}
  • Home/Index.cshtml 画面最初に Html.CreateKnockoutContext() を利用して Knockout MVC 用のインスタンスを作成しており、フォーム部品を作成する際に同インスタンスを利用している点に注目してほしい
@using PerpetuumSoft.Knockout
@model KnockoutMVCSample.Models.Person

@{
    ViewBag.Title = "Home Page";
    var ko = Html.CreateKnockoutContext();
}

@using (Html.BeginForm())
{
    <br /><br />
    <div>
        <h3>knockout.js でのマッピング</h3>
        @Html.LabelFor(m => m.Name) : @ko.Html.TextBox(m => m.Name, new { value = Model.Name, name = "Name" }) @Html.ValidationMessageFor(m => m.Name)<br />
        @Html.LabelFor(m => m.Age) : @ko.Html.TextBox(m => m.Age, new { value = Model.Age, name = "Age" }) @Html.ValidationMessageFor(m => m.Age)<input id="update_button" type="button" value="年齢+1" /> <br />
        <div>
            名前と年齢についてのサマリ:@ko.Html.Span(m => m.Summary, new { id = "summary" })
        </div>
        <br />        
        <input type="submit" name="submit" value="送付" /><br />
    </div>
}
@section scripts
{
    @ko.Apply(Model)
    <script type="text/javascript">
        $(function() {
            $('#update_button').click(function() {
                $('#Age').text(viewModel.Age(viewModel.Age() + 1));
            });
        });
    </script>
}

更に、「注意点」のスクリーンショットを参照して頂ければわかると思うが、Knockout MVC は viewModel という変数名でモデルを生成するため、上記の記載方法で画面上で JavaScript の viewModel を操作することができる。

注意点

Computed 属性を付与しない場合、以下の様に JavaScript 側の計算用ロジックが自動生成されない。
f:id:waritohutsu:20140113134149j:plain

また、モデルクラス(今回は Person.cs) の ToString() メソッドをオーバーライドすると、以下の様に JavaScript 側の自動生成コードがおかしくなってしまう点にも注意が必要だ。
f:id:waritohutsu:20140113134151j:plain

参考

Knockout MVC は以下の様にサンプルが豊富なため、こちらも参照頂きたい。