Part5 ASP.NET Web API を使った SPA 型 Web アプリ開発

WebAPIを使ったSPA型のリスト表示を作ります。コントローラー、Viewを作成します。

$ yo aspnet:MvcController Sample01Controller
$ mkdir Views/Sample01
$ yo aspnet:MvcView Sample01/ShowAllAuthors
$ yo aspnet:MvcView Sample01/ShowAuthorsByState

Sample01ControllerにはまずViewを返すメソッドを実装します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AspNetCorehandson.ApiModels;
using AspNetCorehandson.Models;
using Microsoft.AspNetCore.Mvc;

// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace AspNetCorehandson.Controllers
{
    public class Sample01Controller : Controller
    {
        // GET: /<controller>/ShowAllAuthors
        [HttpGet]
        public IActionResult ShowAllAuthors()
        {
            return View();
        }

        // GET: /<controller>/ShowAuthorsByState
        [HttpGet]
        public IActionResult ShowAuthorsByState()
        {
            return View();
        }
    }
}

ShowAllAuthors.cshtmlと ShowAuthorsByState.cshtmlはひとまずタイトルだけ記述します。デバッグ実行して、このページにアクセスできることを確認します。

@{ ViewData["Title"] = "全著者データの一覧"; }

<h4>全著者データの一覧</h4>
@{ ViewData["Title"] = "州による著者データの検索"; }

<h4>州による著者データの検索</h4>

ControllerにWebAPIのメソッドを追加します。ASP.NET Core MVC にはWebAPIも含まれており、同じControllerの中に混在させることもできます。

[HttpGet]
public IList<Author> GetAllAuthors()
{
    using (var pubs = new PubsEntities())
    {
        return pubs.Authors.ToArray();
    }
}

これで、 http://localhost:xxxx/Sample01/GetAllAuthors にアクセスするとJSONが表示されるはずです。なお、ASP.NET Core MVC のRTMより、デフォルトでJSONのフィールド名が小文字始まりになっています。こちらのハンズオンではこのまま進め、JavaScript側でも小文字始まりとして扱うことにします。

ここで、Authorは全てのプロパティを返すのではなく、一覧表示用に一部のプロパティだけ返すようにしてみましょう。ViewModels/AuthorOverview.csを作成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCorehandson.ViewModels
{
    public class AuthorOverview
    {
        public string AuthorId { get; set; }
        public string AuthorName { get; set; }
        public string Phone { get; set; }
        public string State { get; set; }
        public bool Contract { get; set; }
    }
}

さきほど実装したGetAllAuthorsメソッドを次のように書き直します。

[HttpGet]
public IList<AuthorOverview> GetAllAuthors()
{
    using (var pubs = new PubsEntities())
    {
        return pubs.Authors
            .Select(a => new AuthorOverview
            {
                AuthorId = a.AuthorId,
                AuthorName = a.AuthorFirstName + " " + a.AuthorLastName,
                Phone = a.Phone,
                State = a.State,
                Contract = a.Contract
            })
            .ToArray();
    }
}

ここからクライアントサイドの実装に入ります。 ShowAllAuthors.cshtml をまず次のように修正します。


@{ ViewData["Title"] = "全著者データの一覧"; }

<h4>全著者データの一覧</h4>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            $.getJSON("/Sample01/GetAllAuthors", function (result) {
                console.debug(result);
            });
        });
    </script>
}

動作確認できたら、knockout.jsを利用して表示することにします。まず、knockout.jsをインポートするためのShared Viewを /Views/Shared/_ImportsLibraryKnockout.cshtml に定義します。

<script src="https://ajax.aspnetcdn.com/ajax/knockout/knockout-3.3.0.js"></script>

ShowAllAuthors.cshtml はこうなります、

@{ ViewData["Title"] = "全著者データの一覧"; }

@section Libraries {
    @Html.Partial("_ImportsLibraryKnockout")
}

<h4>全著者データの一覧</h4>

<div class="table-responsive">
    <table class="table table-condensed table-striped table-hover">
        <thead>
            <tr>
                <th>著者ID</th>
                <th>著者名</th>
                <th>電話番号</th>
                <th></th>
                <th>契約有無</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: authors">
            <tr>
                <td data-bind="text: authorId"></td>
                <td data-bind="text: authorName"></td>
                <td data-bind="text: phone"></td>
                <td data-bind="text: state"></td>
                <td>
                    <input type="checkbox" disabled data-bind="checked: contract" />&nbsp;
                    <text data-bind="text: (contract ? '契約あり' : '契約なし')"></text>
                </td>
            </tr>
        </tbody>
    </table>
</div>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            var viewModel = {
                authors: ko.observableArray()
            };
            ko.applyBindings(viewModel);

            $.getJSON("/Sample01/GetAllAuthors", function (result) {
                viewModel.authors(result);
            });
        });
    </script>
}

次に州によるフィルタリングを実装します。州の一覧を返すAPIと、指定された州に属する著者の一覧を取得するAPIをSample01Controllerに追加します。

[HttpGet]
public IList<string> GetStates()
{
    using (var pubs = new PubsEntities())
    {
        var query = pubs.Authors.Select(a => a.State).Distinct();
        return query.ToArray();
    }
}

[HttpGet]
public IList<AuthorOverview> GetAuthorsByState(string state)
{
    if (Regex.IsMatch(state, "^[A-Z]{2}$") == false) throw new ArgumentOutOfRangeException(nameof(state));

    using (var pubs = new PubsEntities())
    {
        var query = pubs.Authors.Where(a => a.State == state)
                    .Select(a => new AuthorOverview()
                    {
                        AuthorId = a.AuthorId,
                        AuthorName = a.AuthorFirstName + " " + a.AuthorLastName,
                        Phone = a.Phone,
                        State = a.State,
                        Contract = a.Contract
                    });
        return query.ToArray();
    }
}

AJAX関連の設定を入れる場合、_Layout.cshtml に記述するとよいだろう。ここでは、HTTP GETでのキャッシュの無効化と集約例外ハンドラを設定している。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <meta name="viewport" content="width=device-width, intial-scale=1.0" />

    <script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">

    <script type="text/javascript">
    $(function () {
        $.ajaxSetup({
            cache: false,
            error: function (xhr, status, err) { alert("通信エラーが発生しました。"); } // 集約通信例外ハンドラ
        });

        window.onerror = function (message, url, lineNumber) {
            console.log(message);
            var msg = "処理中にエラーが発生しました。" + message;
            alert(msg);
            return true;
        };
    });
    </script>

    @RenderSection("Libraries", required: false)
    //略

ShowAuthorsByState.cshtml を次のように実装します。

@{ ViewData["Title"] = "州による著者データの検索"; }

@section Libraries {
    @Html.Partial("_ImportsLibraryKnockout")
}

<h4>州による著者データの検索</h4>

<div>
    <select id="ddlStates" data-bind="options: states"></select>
    <button id="btnShowAuthors">データ表示</button>
</div>

<hr />

<div class="table-responsive">
    <table id="tblAuthors" class="table table-condensed table-striped table-hover">
        <thead>
            <tr>
                <th>著者ID</th>
                <th>著者名</th>
                <th>電話番号</th>
                <th></th>
                <th>契約有無</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: authors">
            <tr>
                <td data-bind="text: authorId"></td>
                <td data-bind="text: authorName"></td>
                <td data-bind="text: phone"></td>
                <td data-bind="text: state"></td>
                <td>
                    <input type="checkbox" disabled data-bind="checked: contract" />&nbsp;
                    <text data-bind="text: (contract ? '契約あり' : '契約なし')"></text>
                </td>
            </tr>
        </tbody>
    </table>
</div>

@section Scripts {
    <script type="text/javascript">
        $(function () {
            $("#tblAuthors").hide(); // css('display', 'none') と同じ

            // 後から値を入れたい場合には、ko.observable() と ko.observableArray() を割り当てておく
            var viewModel = {
                states: ko.observableArray(),
                authors: ko.observableArray()
            };
            ko.applyBindings(viewModel);

            // サーバから州一覧を取り寄せてバインド
            $.getJSON("/Sample01/GetStates", function (result) {
                viewModel.states(result);
            });

            $("#btnShowAuthors").click(function () {
                // クエリ文字列を引数に渡すには、第二パラメータにオブジェクトを渡す
                $.getJSON("/Sample01/GetAuthorsByState", { state: $("#ddlStates").val() }, function (result) {
                    viewModel.authors(result); // データを observableArray に流し込み
                    $("#tblAuthors").show(); // css('display', 'block') と同じ
                });
            });
        });
    </script>
}

results matching ""

    No results matching ""