読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET MVC 4 Beta で追加された Single Page Application を試す

ASP.NET MVC 4 Beta で追加された Web API プロジェクトを試す - しばやん雑記 では Web API プロジェクトを試しましたが、今回はもう一つ追加された Single Page Application を試してみようと思います。

プロジェクトの作成は今までと同じで、テンプレートとして Single Page Application を選択するだけです。

ちなみに Single Page Application というのは名前の通りなんですが、1 つの画面で追加・編集・削除の処理を行うアプリケーションのことです。

ASP.NET MVC 3 でも Ajax を使えば同様のことは実現できていたんですが、MVC 4 の Single Page Application ではサーバ側に Web API を、クライアント側に knockout.js を使うことで実現しています。

本筋に戻って、プロジェクトの作成が出来たら Web API の時と同じように実行してみましょう。

これまた同じように手順が書かれているので引用してきました。

  1. Add model classes to your project
    • A model class holds data and behaviors related to your application. You'll find an example model class already in your project at \Models\TodoItem.cs
  2. Use scaffolding to generate an initial User Interface (UI)
    • Build your solution (Build -> Build Solution). Then, open the Add Controller dialog by right-clicking on the Controllers folder, and choose Add ▶ Controller.
  3. Extend and customize your code
    • The scaffolded application is intended only to help you get started quickly. You'll want to modify the generated code to customize the UI and add extra features. For samples and tutorials, see www.asp.net/single-page-application.

要約するとモデルクラスを書いて、ビルドしてからスキャフォールディングでコントローラを生成して、あとは生成されたコードをカスタマイズしろということです。モデルクラスは TodoItem クラスがサンプルで用意されてるので、それで試すと良い感じですね。

とりあえず TodoItem クラスを見ておきましょうか。

public class TodoItem
{
    public int TodoItemId { get; set; }
    [Required]
    public string Title { get; set; }
    public bool IsDone { get; set; }
}

何の変わり映えもしない、いたって普通の POCO なモデルですね。よく見ると TodoItem.cs のコメントにいろいろ書いています。

// To quickly get started building a Single Page Application based on the following model
// class, build your solution (Build -> Build Solution), then right-click on the "Controllers" folder, 
// choose Add -> Controller, and set the following options:
//
//  * Controller name:    TasksController
//  * Template:           Single Page Application with read/write actions and views, using Entity Framework
//  * Model class:        TodoItem (MvcApplication13.Models)
//  * Data Context class: Choose <New data context...>, then click OK
//
// Afterwards, launch your application (Debug -> Start Debugging), then browse to the URL /Tasks
// For more information, see http://go.microsoft.com/fwlink/?LinkId=238343

要約すると、フルスキャフォールディングを使ってコントローラ名 TasksController のテンプレートが SPA を指定して生成してねということですね。

それではその通りにやって生きましょう。まずは Controller ディレクトリを右クリックして「追加」->「Controller」の順にクリックして、スキャフォールディングのダイアログを表示します。

その時にモデルクラスがエラーになった場合はビルドをしてもう一度実行してください。後は以下のように項目を埋めていけば OK です。

これで必要なファイルがすべて生成されたので、ひとまず実際に動作させてみましょう。トップページが表示されたら /Tasks へ移動するか、トップページにもリンクがあるのでそれを使って遷移してください。

/Tasks にアクセスするとこんな感じです。「Create TodoItem」をクリックすると入力用のフォームが表示されます。

入力して「Save」をクリックするとサーバに POST されて一覧が表示されます。

登録された項目をクリックすると編集用のフォームが表示されます。

値が変更されると「Save」ボタンが押せるようになります。そして「Delete」をクリックするとこの項目の削除が可能です。

動きとしてはよくある感じではありますが、面白いことにこれらの画面遷移は全て knockout.js を使って行われています。そして注目すべきは URL ですね。

ハッシュを使うことでブラウザの戻る、進むボタンが動作するようになっています。ちなみに HTML5 の History API に対応したブラウザの場合は少し異なります。

Chrome 17 の場合は通常の URL と区別がつきませんね。ちなみに履歴管理には MS 謹製の nav.js が使われていますが、単体で使うことはあまり考えない方がよさそうです。

IE も 10 から History API に対応する予定なので、公開を楽しみに待ちましょう。

これで Single Page Application の基本的な使い方の説明は終わりなんですが、SPA テンプレートを選ぶと TasksController.cs と TasksController.TodoItem.cs の 2 ファイルが生成されますので、中身を確認しておきます。

TasksController.cs

public partial class TaskController : DbDataController<MvcApplication13.Models.TaskContext>
{
    // Any code added here will apply to all entity types managed by this data controller
}

// This provides context-specific route registration
public class TaskRouteRegistration : AreaRegistration
{
    public override string AreaName
    {
        get { return "Task"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        RouteTable.Routes.MapHttpRoute(
            "Task", // Route name
            "api/Task/{action}", // URL with parameters
            new { controller = "Task" } // Parameter defaults
        );
    }
}

TasksController.TodoItem.cs

public partial class TaskController : DbDataController<MvcApplication13.Models.TaskContext>
{
    public IQueryable<MvcApplication13.Models.TodoItem> GetTodoItem() {
        return DbContext.TodoItem.OrderBy(t => t.TodoItemId);
    }

    public void InsertTodoItem(MvcApplication13.Models.TodoItem entity) {
        InsertEntity(entity);
    }

    public void UpdateTodoItem(MvcApplication13.Models.TodoItem entity) {
        UpdateEntity(entity);
    }

    public void DeleteTodoItem(MvcApplication13.Models.TodoItem entity) {
        DeleteEntity(entity);
    }
}

中身は単純に TasksController の部分クラスでした。注目すべきは TasksController が DbDataController クラスを継承しているところですね。型パラメータとして DbContext を受け取り、あとは DbDataController クラスに丸投げするだけで API が出来てしまうというお手軽さです。

ApiController はモデル周りを自分で作る必要がありましたが、DbDataController では Entity Framework の DbContext API 限定ですが全てやってくれます。ちなみに LinqToEntitiesDataController というクラスも用意されていて、こっちは型パラメータとして ObjectContext を指定する形になっています。

まだ試せていないのですが、部分クラスとなっているので 1 つのコントローラで複数のエンティティを操作することも可能だと思います。

そしてクライアントサイドも見ていきます。

@{
    ViewBag.Title = "TodoItem";
    Layout = "~/Views/Shared/_SpaLayout.cshtml";
}

<div data-bind="visible: editingTodoItem">
    @Html.Partial("_Editor")
</div>

<div data-bind="visible: !editingTodoItem()">
    @Html.Partial("_Grid")
</div>

<div class="message-info message-success" data-bind="flash: { text: successMessage, duration: 5000 }"></div>
<div class="message-info message-error" data-bind="flash: { text: errorMessage, duration: 20000 }"></div>

<script type="text/javascript" src="@Url.Content("~/Scripts/TodoItemViewModel.js")"></script>
<script type="text/javascript">
    $(function () {
        upshot.metadata(@(Html.Metadata<MvcApplication13.Controllers.TaskController>()));

        var viewModel = new MyApp.TodoItemViewModel({
            serviceUrl: "@Url.Content("~/api/Task")"
        });
        ko.applyBindings(viewModel);
    });
</script>

基本的には knockout.js, nav.js, upshot.js で実装されています。upshot.js というのは MS 謹製の Web API がデータソースの Entity Framework 的な何かと考えればいい気がします。間違ってるかも。

upshot.js は metadata を埋め込んで使いますが、埋め込み用のメタデータを生成する HTML ヘルパーが用意されています。ちなみにメタデータは以下のような形式です。

{"TodoItem:#MvcApplication13.Models":{"key":["TodoItemId"],"fields":{"IsDone":{"type":"Boolean:#System"},"Title":{"type":"String:#System"},"TodoItemId":{"type":"Int32:#System"}},"rules":{"Title":{"required":true}},"messages":{}}}

そして MyApp.TodoItemViewModel はスキャフォールディングで自動生成されます。興味がある人は読んでみると良いかもしれないですが、自動生成コードなのであまり気にしない方がいいと思います。

upshot.js の情報が少なすぎるので、また揃ってきたらクライアントサイドを調べたいと思います。