ASP.NET MVC5で、「X.PagedList」というパッケージを使用してページング機能を追加してみました。
日本語の情報が少なく苦戦したので、これから実装する方の参考になれば嬉しいです。
今回実装する処理内容
- 検索窓に文字を入力して「検索」ボタンをクリック→DBからデータ取得→結果を画面に表示
- EntityFrameworkではなく直接SQLを実行してデータを取得
- 検索中はAjaxでクルクル(ローディング)画像を表示する
Ajaxでローディング画像を表示する方法については以下の記事を参照してください。
環境
- Windows10
- Visual Studio Community 2019
- .NET Framework 4.8
準備
ページング機能を使うためにNuGetパッケージを追加します。
「ツール」→「NuGetパッケージマネージャ」→「ソリューションのNuGetパッケージの管理」をクリックし、次の画像の通り「X.PagedList.MVC」をインストールしましょう。
自動的にX.PagedList、X.PagedList.MVC.Commonもインストールされます。
また、今回はDBからデータを抽出して表示するため、適当なデータを格納したusersテーブルを用意します。
コードと解説
まずコントローラーです。ビューで「検索」ボタンがクリックされたらDBからユーザーを抽出し、検索結果をIPagedListに変換してから部分ビューを返します。
using System.Collections.Generic;
using System.Web.Mvc;
using WebApplication.Models;
using X.PagedList;
namespace WebApplication.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index(string searchValue, int? page)
{
if (searchValue != null || page != null)
{
// 1ページに表示する最大件数
int pageSize = 5;
// ページ数がnullだったら1を設定
int pageNumber = page ?? 1;
if (pageNumber < 1) pageNumber = 1;
// DBからデータを検索して取得
List<PagedListViewModel>users = PagedListViewModel.SerachUsers(searchValue);
// ページング処理に対応するためIPagedListに変換
IPagedList<PagedListViewModel> result = users.ToPagedList(pageNumber, pageSize);
// ViewBagに検索文字列を格納する
ViewBag.searchValue = searchValue;
return PartialView("_IndexPartial", result);
}
return View();
}
}
}
ページの表示に使用するモデルとユーザーの検索処理です。便宜上1つのクラスにまとめていますが本当は分けた方がいいです。
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
namespace WebApplication.Models
{
public class PagedListViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int? Page { get; set; } = 1;
public string SearchValue { get; set; } = string.Empty;
// ユーザー検索処理
public static List<PagedListViewModel> SerachUsers(string searchValue)
{
var result = new List<PagedListViewModel>();
var connectionString = "<接続文字列>";
var sql = "SELECT * FROM users WHERE Name LIKE @searchValue";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
// LIKE句を使ってあいまい検索
command.Parameters.AddWithValue("@searchValue", "%" + searchValue + "%");
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
result.Add(new PagedListViewModel
{
Id = Convert.ToInt32(reader["id"]),
Name = Convert.ToString(reader["name"]),
Age = Convert.ToInt32(reader["age"])
});
}
}
return result;
}
}
}
}
ビューです。ローディング画像を表示するためにAjaxを使っています。
<h2>ユーザー名検索</h2>
@* Ajax.BeginFormの使用 *@
@using (Ajax.BeginForm("Index", "Home", new AjaxOptions
{
LoadingElementId = "loading",
HttpMethod = "Get",
UpdateTargetId = "target",
OnBegin = "onBegin",
OnComplete = "onComplete"
}))
{
<input type="text" class="form-control" name="searchValue">
<button class="btn btn-primary" value="search" name="search" type="submit">検索</button>
@* ローディング画像の表示(初期状態は非表示) *@
<img src="@Url.Content("~/Content/Image/ajax-loader.gif")"
id="loading" style="display:none;" />
}
@* 部分ビュー表示箇所 *@
<div id="target"></div>
@* Ajax.BeginFormを使うためのjqueryファイルの呼び出し *@
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
@* ローディング画像の表示切り替え用スクリプト *@
<script>
function onBegin() {
$('#search').prop('disabled', true);
};
function onComplete() {
$('#search').prop('disabled', false);
};
$(document).ready(function () { });
</script>
部分ビューです。検索結果を表示します。usingの追加と、IPagedListでモデルを参照することを忘れないようにしましょう。
@using X.PagedList.Mvc.Common;
@using X.PagedList.Mvc;
@using WebApplication.Models;
@model X.PagedList.IPagedList<PagedListViewModel>
<hr />
@if (Model == null) { }
@if (Model.TotalItemCount > 0)
{
<p>
表示件数: @Model.FirstItemOnPage ~ @Model.LastItemOnPage 件
(全 @Model.TotalItemCount 件)
</p>
}
else
{
<p>表示件数: 0件</p>
}
<table class="table table-bordered">
<thead>
<tr>
<th>
<div class="text-center">ID</div>
</th>
<th>
<div class="text-center">ユーザー名</div>
</th>
<th>
<div class="text-center">年齢</div>
</th>
</tr>
</thead>
@if (Model.TotalItemCount > 0)
{
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@Html.DisplayFor(m => item.Id)</td>
<td>@Html.DisplayFor(m => item.Name)</td>
<td>@Html.DisplayFor(m => item.Age)</td>
</tr>
}
</tbody>
}
else
{
<tbody>
<tr>
<td colspan="3" align="center">データがありません</td>
</tr>
</tbody>
}
</table>
@* ページング機能の実装 *@
@Html.PagedListPager(
Model,
page => Url.Action("Index",
new PagedListViewModel { Page = page, SearchValue = ViewBag.searchValue }),
PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing(new PagedListRenderOptions()
{
LiElementClasses = new string[] { "page-item" },
PageClasses = new string[] { "page-link" },
},
new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "target" })
)
最後にPagedListPagerを使ってページング機能を実装しています。
ポイントとしてはまず、ページを移動してもテキストボックスの値を使い回せるように、searchValueの値をビューに渡しています。(もっといい方法があるのかも…)
PagedListRenderOptionsではページングボタンのクラスを指定しています。ここを変えることでBootstrapによるボタンの見た目を変更できます。詳しくは作者のドキュメントを参照してください。
AjaxOptionsで、部分ビューの表示箇所を指定しています。
PagedListPagerで部分ビューとBootstrapのデザイン変更を行うやり方でかなり苦戦しましたが,
stackoverflowで参考になるページがあり助かりました。↓
エラーが出たら
以下のように「アセンブリnetstandard, Version=2.0.0.0」への参照追加が求められることがあります。
その場合はWeb.Configの<system.web>を次のように書き換えてから、ビルドすると解決します。
<system.web>
<compilation debug="true" targetFramework="4.8">
<assemblies>
<add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"/>
</assemblies>
</compilation>
<httpRuntime targetFramework="4.8" />
</system.web>
参考:https://github.com/dotnet/standard/issues/542
実行結果
テキストボックスに試しに「佐藤」と入れて検索してみます。
佐藤さんは6人いるので2ページに分かれて表示されました。
2ページ目。
テキストボックスに何も入れないと全データが出力されます。
テーブルに存在しない名前で検索した場合。