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

1.21 jigowatts

Great Scott!

ASP.NET MVCはじめました~検索結果をjqGridに表示する

概要

自前でページング処理を実装してきましたが、jqGridを使って面倒な処理を丸投げしてしまいます。


ASP.NET MVCはじめました~検索画面を作る - 1.21 jigowatts

ASP.NET MVCはじめました~Ajax.BeginFormでデータグリッドとページング - 1.21 jigowatts

環境

Visual Studio 2010
ASP.NET MVC2
jqGrid

今回の要件は

  1. 検索結果データを非同期で取得する
  2. jqGridに表示する
  3. ページングやソート機能を実装する

として、サンプルコードを書いてみました。

実装

JavaScriptファイルの用意

検索ボタン押下イベントとjqGridの設定を書きます。
JavaScriptを外出しにしたため、URLを生成する箇所で躓きました(後述)。
■Mvc2App/Scripts/Libs/PeopleJqgridSearch.js

$(function () {

    //検索ボタン押下イベント
    $('#btnSearch').click(function () {
        var $ctrls = $("#searchGrid");
        var dataType = function (postData) {
            var url = getPath("People/GetData");
            var arg = {
                'Name': $('#condition_Name').val(),
                'Address': $('#condition_Address').val(),
                'PhoneNumber': $('#condition_PhoneNumber').val(),
                'UpdatedBy': $('#condition_UpdatedBy').val(),
                'UpdateDate': $('#condition_UpdateDate').val(),
                'sidx': postData.sidx,
                'sord': postData.sord,
                'page': postData.page,
                'rows': postData.rows
            };
            $.post(
                url,
                arg,
                function (data) {
                    $ctrls[0].addJSONData(data);
                },
                "json"
            );
        }
        $ctrls.jqGrid("setGridParam", { datatype: dataType });
        $ctrls.trigger("reloadGrid");   // <=ここで処理が走る
    });


    //*グリッド定義
    $('#searchGrid').jqGrid({
        'loadError': function (xhr, status, error) {
            alert(xhr.statusText)
        },
        datatype: "local",
        width: 1150,
        height: 96,
        rowNum: 3,//表示件数:default20
        //hiddengrid: true,
        hidegrid: true,
        multiselect: false,
        shrinkToFit: false,
        rownumbers: true,
        showPager: true,
        viewrecords: true,
        caption: " Search result",
        pager: '#pager',
        jsonReader: {
            page: "page",
            total: "total",
            records: "records",
            root: "data"
        },
        colNames: ['Name', 'Address', 'PhoneNumber', 'UpdatedBy', 'UpdateDate'],
        colModel: [{ 'name': 'Name' }, { 'name': 'Address' }, { 'name': 'PhoneNumber' }, { 'name': 'UpdatedBy' }, { 'name': 'UpdateDate'}]

    });
    $("#searchGrid").jqGrid('navGrid', "#pager", { add: false, edit: false, del: false, search: false, refresh: false });

});
コントローラーの用意

ビューの初期表示時用アクションメソッドと、検索結果データを取得するAjax呼び出し用アクションメソッドを記述します。検索結果はJsonメソッドJSON形式にシリアル化して返します。
■Mvc2App/Controllers/PeopleController.cs

[HttpGet]
public ActionResult JqgridSearch()
{
    return View();
}

[HttpPost]
public ActionResult GetData(PeopleSearchConditionModel condition)
{
    if (Request.IsAjaxRequest()) 
    {
        var model = _service.JqgridSearch(condition);
        return Json(model);
    }

    return RedirectToAction("Show", "Error");
}
サービスインタフェースとサービスクラスの用意

■Mvc2App/Services/PeopleService.cs

public interface IPeopleService
{
    JqgridPeopleSearchViewModel JqgridSearch(PeopleSearchConditionModel condition);
}

■Mvc2App/Services/PeopleService.cs

public JqgridPeopleSearchViewModel JqgridSearch(PeopleSearchConditionModel condition)
{
    var searcher = new PeopleSearcher();
    return searcher.JqgridSearch(condition);
}
検索フレームワークの用意

jqGridの表示用ビューモデルに詰め替えます。
■Mvc2App/Framework/Search/PeopleSearcher.cs

public class PeopleSearcher
{
    public JqgridPeopleSearchViewModel JqgridSearch(PeopleSearchConditionModel condition)
    {
        var data = GetAllData(condition);
        var list = new List<JqgirdPeopleViewModel>();
        foreach (var item in data)
        {
            var rec = new JqgirdPeopleViewModel();
            rec.Name  = item.Name;
            rec.Address = item.Address;
            rec.PhoneNumber = item.PhoneNumber;
            rec.UpdatedBy = item.UpdatedBy;
            rec.UpdateDate = item.UpdateDate.ToShortDateString();
            list.Add(rec);
        }
        var helper = new SearchHelper<JqgirdPeopleViewModel>(list.ToArray(), condition);
        helper.Order();
        var model = new JqgridPeopleSearchViewModel()
        {
            data = helper.GetData(),
            page = helper.GetPage(),
            records = helper.GetRecords(),
            total = helper.GetTotal(),
            SysMessage = helper.GetTotalCountMessage()
        };

        return model;
    }
}

ソート機能を追加しました。
■Mvc2App/Framework/Search/SearchHelper.cs

public class SearchHelper<T>
{
    private T _instance;
    private T[] _data;
    private SearchConditionModelBase _condition;

    public SearchHelper(T[] data, SearchConditionModelBase condition)
    {
        _instance = Activator.CreateInstance<T>();
        _data = data;
        _condition = condition;
    }

    ...

    public void Order()
    {
        if (!string.IsNullOrEmpty(_condition.Sidx))
        {
            if ("asc".Equals(_condition.Sord))
            {
                _data = OrderBy(_data, _condition.Sidx);
            }
            else if ("desc".Equals(_condition.Sord))
            {
                _data = OrderByDescending(_data, _condition.Sidx);
            }
        }
    }

    private T[] OrderBy(T[] data, string sortCol)
    {
        Type type = _instance.GetType();
        var prop = type.GetProperty(sortCol);

        data = data.OrderBy(x => prop.GetValue(x, null)).ToArray();

        return data;
    }

    private T[] OrderByDescending(T[] data, string sortCol)
    {
        Type type = _instance.GetType();
        var prop = type.GetProperty(sortCol);

        data = data.OrderByDescending(x => prop.GetValue(x, null)).ToArray();

        return data;
    }
}
ビューモデルの用意

■Mvc2App/ViewModels/People/PeopleViewModel.cs

public class JqgirdPeopleViewModel : ViewModelBase 
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string PhoneNumber { get; set; }
    public string UpdatedBy { get; set; }
    public string UpdateDate { get; set; }
}
ビューの用意

アプリケーション名を考慮したURLの生成のため、getPathファンクションを用意しました。
■Mvc2App/Views/Shared/Site.Master

<body>
    <div class="page">
        ...
    </div>
    <script type="text/javascript">
        //参考
        //http: //stackoverflow.com/questions/6796880/how-to-get-asp-net-mvc-3-current-project-name
        var root = '<%: Url.Content("~/") %>';
        function getPath(url) {
            return root + url;
        }
    </script>
</body>

■Mvc2App/Views/People/JqgridSearch.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
 Inherits="System.Web.Mvc.ViewPage<Mvc2App.ViewModels.People.PeopleSearchViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	JqgridSearch
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>JqgridSearch</h2>
    <fieldset>
        <legend>Search Conditions</legend>
            <div class="field-box">
                <div class="field">
                    <span class="field-label">Name</span>
                    <%: Html.TextBoxFor(model => model.condition.Name)%>
                </div>
                <div class="field">
                    <span class="field-label">Address</span>
                    <%: Html.TextBoxFor(model => model.condition.Address)%>
                </div>
                <div class="field">
                    <span class="field-label">PhoneNumber</span>
                    <%: Html.TextBoxFor(model => model.condition.PhoneNumber)%>
                </div>
                <div class="field">
                    <span class="field-label">UpdatedBy</span>
                    <%: Html.TextBoxFor(model => model.condition.UpdatedBy)%>
                </div>
                <div class="field">
                    <span class="field-label">UpdateDate</span>
                    <%: Html.TextBoxFor(model => model.condition.UpdateDate)%>
                </div>
            </div>

            <div id="btnBox1">
                <div id="btnBox2">
                    <input id="btnSearch" type="button" value="Search" />
                </div>                
            </div>
    </fieldset>

    <div>
        <table id="searchGrid"></table>
        <div id="pager"></div>
    </div>
</asp:Content>

<asp:Content ID="ScriptContent" ContentPlaceHolderID="ScriptContent" runat="server">
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/i18n/grid.locale-ja.js")%>" ></script>
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/jquery.jqGrid.src.js") %>"></script>
    <script type="text/javascript" src="<%: Url.Content("~/Scripts/Libs/PeopleJqgridSearch.js") %>"></script>
</asp:Content>

だいぶソースが散らかってきました!*1

実行結果

初期表示時はグリッドには何も表示されません。
f:id:sh_yoshida:20150114034130p:plain
検索結果を取得しグリッドに表示。
f:id:sh_yoshida:20150114034239p:plain
ページング機能とソート機能を実装しました。
2ページ目を表示したところ。
f:id:sh_yoshida:20150114034250p:plain
項目を押下すると並び替えてくれます。
Name項目を昇順で並び替え。
f:id:sh_yoshida:20150114034301p:plain
検索条件を入れての検索。
f:id:sh_yoshida:20150114034311p:plain


今回はまったポイント

上でも書きましたが、URLの生成にはまりました。
今回の例ではPostメソッドの引数のurlに"/People/GetData"と指定したところ、ローカルでデバッグしているときは問題なかったのですがIISにデプロイしたところ404になってしまいました。
というのも、http://localhost:12345/People/GetDataであればアクセスできたのですが、IIS上ではアプリケーション名が付与される設定だったため、本来http://192.168.11.123:80/Mvc2App_deploy/People/GetDataにアクセスすべきところが、http://192.168.11.123:80/People/GetDataにアクセスしてしまい、404となってしまいました。

いろいろ調べてみた結果、下記を参考にgetPathファンクションを作成して対応しています。このファンクションをSite.Masterに仕込んでおくことで、仮想アプリケーションルートを含めたパスの生成が可能になりました。
javascript - How to get Asp.Net MVC 3 current project name? - Stack Overflow

JavaScriptをビューに書けばいいんでしょうけど、別の方が見やすいので一手間かけました。こんなときどうする??


ASP.NET MVCはじめました~検索画面を作る - 1.21 jigowatts

ASP.NET MVCはじめました~Ajax.BeginFormでデータグリッドとページング - 1.21 jigowatts

*1:この記事の内容だけじゃ動かないので他の記事と合わせて補完してください。そのうちどうにかしたい!