1.21 jigowatts

Great Scott!

ASP.NET MVC オートコンプリート

概要

あると便利!jQuery UIのAutocomplete(オートコンプリート)をASP.NET MVCで実装してみます。
今回のサンプルは入力した文字列に応じて入力候補が表示され、選択した値が各項目にセットされるというものです。

f:id:sh_yoshida:20160612013611g:plain

環境

Visual Studio Community 2015 Update 2
ASP.NET MVC5
jQuery UI

実装

コントローラー

必要なのはJSONデータなので、JsonメソッドによりJsonResultオブジェクトを返します。
既定ではHTTP GETリクエストではJSONデータを返せないようです。
Controllers\HomeController.cs

//ビューの表示
public ActionResult Create()
{
    return View();
}

public JsonResult AutoComplete(String name)
{
    var data = _service.GetPeopleByName(name);
    return Json(data);
}

明示的にGETリクエストを許可するには以下のようにする必要があります。

return Json(data, JsonRequestBehavior.AllowGet);

ワーカーサービス

多くの場合、リポジトリクラスからデータベースにアクセスし必要なデータを取得すると思いますが、今回はサンプルのため擬似コードで割愛。
Services\People\PeopleService.cs

public class PeopleService : IPeopleService
{
    public IEnumerable<Person> GetPeopleByName(string name)
    {
        var data = GetData();
        var result = data.Where(c => c.Name.StartsWith(name, ignoreCase: true, culture: ci));

        return result;
    }
}

モデル

Personクラスをベースにします。
Models\Person.cs

public class Person
{
    public int Id { get; set; }
    public String Name { get; set; }
    public String Email { get; set; }
    public String Birthday { get; set; }
}

ビュー

簡単な入力フォーム上でName項目に入力した値で動作するようにしています。
Views\Home\Create.cshtml

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Person</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Birthday, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Birthday, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Birthday, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

JavaScript

$(function () {
    $('#Name').autocomplete({
        source: function (request, response) {
            var param = { name: $('#Name').val() };

            $.ajax({
                url: '@Url.Content("~/Home/AutoComplete")',
                data: JSON.stringify(param),
                datatype: "json",
                type: "POST",
                contentType: "application/json",
                dataFilter: function (data, type) { return data },
                success: function (data, dataType) {
                    response($.map(data, function (item) {
                        return {
                            label: "ID:" + item.Id + " Name:" + item.Name + " Email:" + item.Email + " Birthday:" + item.Birthday,
                            value: item.Name,
                            extend: item
                        };
                    }));
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert('error');
                }
            });
        },
        select: function (event, ui) {
            $('#Email').val(ui.item.extend.Email);
            $('#Birthday').val(ui.item.extend.Birthday);

        }
    });
});

実行結果

Name項目に入力した文字列に対して入力候補が絞られていきます。
最終的に選択した値が各項目にセットされました!
f:id:sh_yoshida:20160612013611g:plain

ASP.NET Web APIという選択

ASP.NET MVCはRPCベースのスタイルであるのに対し、ASP.NET Web APIはRESTfullスタイルが既定(RPCもイケル)です。
ASP.NET MVCではJSONデータが返せるため、あまりメリットを感じないのですが、本来はWeb APIとして提供した方がいいケースもあります。

上記のコードを少し変更しつつ、ASP.NET Web APIバージョンも実装してみます。

実装

APIコントローラー

MVC6では統合されたようですが、今回はMVC5なのでASP.NET Web APIコントローラーはまだSystem.Web.HttpアセンブリのApiControllerを継承しています。
Api\PeopleController.cs

public class PeopleController : ApiController
{
    public IEnumerable<Person> GetByName(String name)
    {
        return _service.GetPeopleByName(name);
    }
}

プロジェクト作成時にテンプレートでWeb APIを選択していない場合、APIコントローラーを追加しただけでは動かないため、以下を追加します。

既定のルートの設定

App_Start\WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}
Web APIの既定ルートを登録

Global.asax

protected void Application_Start()
{
    //追記
    GlobalConfiguration.Configure(WebApiConfig.Register);
    ...
}

ビュー

GETリクエストでhttp://{server}/api/{apicontroller}へアクセスしJSONデータを取得します。
JavaScript

$(function () {
    $('#Name').autocomplete({
        source: function (request, response) {
            var param = { name: $('#Name').val() };

            $.getJSON('@Url.Content("~/api/people")', param)
            .done(function (data) {
                response($.map(data, function (item) {
                    return {
                        label: "ID:" + item.Id + " Name:" + item.Name + " Email:" + item.Email + " Birthday:" + item.Birthday,
                        value: item.Name,
                        extend: item
                    };
                }));
            })
            .fail(function (jqXHR, textStatus, err) {
                alert('error : ' + err);
            });
        },
        select: function (event, ui) {
            $('#Email').val(ui.item.extend.Email);
            $('#Birthday').val(ui.item.extend.Birthday);
        }
    });
});

まとめ

見た目は同じですが、実装をASP.NET Web APIに変更してみました。
どちらを選択するかは仕様によると思いますが、MVC6で統合された経緯を見る限りどちらも使いこなせたほうが良さそうですね。
ASP.NET Coreを視野に入れつつASP.NET Web APIも学習してみようっと。

参考

プログラミングASP.NET MVC 第3版 ASP.NET MVC 5対応版

プログラミングASP.NET MVC 第3版 ASP.NET MVC 5対応版

.NET開発テクノロジ入門 2014年版 VisualStudio2013対応版 (MSDNプログラミングシリーズ)

.NET開発テクノロジ入門 2014年版 VisualStudio2013対応版 (MSDNプログラミングシリーズ)