[ASP Net MVC] 使用MvcPaging實作分頁功能

使用MvcPaging實作分頁功能

前言

 

分頁功能不僅在於畫面的美觀,其實也牽涉到效能問題。通常查詢功能會伴隨著大量資料回傳,此時若不實作分頁功能,當資料量大時必定是災難的開始,所以分頁功能是必須且必要的。當然我們可以自行實作這一切,包括畫面設計及依前端回傳頁碼配合最大頁面資料數量撈出指定數量資料,最後呈現資料於畫面中且調整畫面目前頁碼;但既然已有前輩把輪子都做好了,我們何必要再花費時間做相同的事情呢? 此篇將透過MvcPaging套件來實現分頁功能,以下將以未分頁之商品搜尋功能做基礎,套入MvcPaging達到分頁效果。

 

image

 

 

未具分頁功能之商品查詢頁面

 

首先實作一個商品查詢頁面,一般而言就是把資料依條件直接撈出,然後一股腦地傳到頁面上進行呈現。

image

 

首先定義出 View Model (搜尋條件 + 資料結果)

{
    // Properties
    public string ProductName { get; set; }  // 搜尋條件1

    public string Manufactory { get; set; }  // 搜尋條件2

    public IEnumerable<Product> Products { get; set; }  // 符合條件資料


    // Constructors
    public ProductIndexViewModel()
    {
        ProductName = string.Empty;
        Manufactory = string.Empty;
    }
}

 

Controller的工作就是依條件從DB取出所有符合的商品傳至View中 (Controller/ProductController.cs)

{
    private MyDbContext db = new MyDbContext();

    public ActionResult Index()
    {
        // 進入搜尋頁面 不主動撈取資料
        ProductIndexViewModel viewModel = new ProductIndexViewModel();
        return View(viewModel);
    }

    [HttpPost]
    public ActionResult Index(ProductIndexViewModel viewModel)
    {
        // 搜尋條件為 = 產品名稱 AND 生產工廠
        IQueryable<Product> products = db.Products;

        // 如果有輸入產品名稱作為搜尋條件時
        if (!string.IsNullOrWhiteSpace(viewModel.ProductName))
        { products = products.Where(p => p.Name.Contains(viewModel.ProductName)); }

        // 如果有輸入生產工廠作為搜尋條件時
        if (!string.IsNullOrWhiteSpace(viewModel.Manufactory))
        { products = products.Where(p => p.Manufactory.Contains(viewModel.Manufactory)); }

        // 回傳搜尋結果
        viewModel.Products= products.Include(p => p.ProductType)
                                    .OrderBy(p => p.ProductId);

        return View(viewModel);
    }
}

 

最後將View Model中所有資料如實呈現於View中 (Views/Product/Index.cshtml)

<h3>Search Product</h3>

@using (Html.BeginForm("Index", "Product", FormMethod.Post, new { id = "ProductList" }))  
{
    <table class="table-condensed">
        <tr>
            <td>名稱</td> <td> @Html.EditorFor(model => model.ProductName)</td>
            <td>工廠</td> <td> @Html.EditorFor(model => model.Manufactory) </td>
            <td> <input type="submit" value="Search" /> </td>
        </tr>
    </table>

    if (Model.Products != null && Model.Products.Count() > 0)
    {
    
    <table class="table">
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Description)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Manufactory)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Price)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().ProductType.Name)
            </th>
            <th></th>
        </tr>

    @foreach (var item in Model.Products) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Manufactory)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ProductType.Name)
            </td>
            <td>
                @Html.ActionLink("加入購物車", "AddInCart", new { id = item.ProductId })
            </td>
        </tr>
    }

    </table>

    }
}

 

 

使用MvcPaging實作分頁

 

首先從NuGet中下載MvcPaging套件

image

 

在修改前可以稍微想想分頁需要哪些資訊? 以下將在後續實作中逐一加入以達分頁功能需求。

1. 單頁可容納之資料筆數 (PageSize)

2. 目前顯示頁碼(Page)

3. 提供使用者點選切換頁面之控制項(Pager)

 

於ViewModel中加入頁碼資訊(Page),並且將資料型態由IEnumerable調整為IPagedList

image

{
    // Properties
    public string ProductName { get; set; }  // 搜尋條件1

    public string Manufactory { get; set; }  // 搜尋條件2

    public IPagedList<Product> Products { get; set; }  // 符合條件資料

    public int Page { get; set; }  // 頁碼


    // Constructors
    public ProductIndexViewModel()
    {
        ProductName = string.Empty;
        Manufactory = string.Empty;
        Page = 0;
    }
}

 

於Controller中加入MvcPaging命名空間後,並加入單頁可容納資料筆數(PageSize)參數,最後將撈出資料轉型為IPagedList傳入頁碼單頁容納資料筆數即可。

image

public class ProductController : Controller
{
    private MyDbContext db = new MyDbContext();

    // 單頁可容納之資料筆數(可參數化此數值)
    private const int PageSize = 5;

    public ActionResult Index()
    {
        // 進入搜尋頁面 不主動撈取資料
        ProductIndexViewModel viewModel = new ProductIndexViewModel();
        return View(viewModel);
    }

    [HttpPost]
    public ActionResult Index(ProductIndexViewModel viewModel)
    {
        // 搜尋條件為 = 產品名稱 AND 生產工廠
        IQueryable<Product> products = db.Products;

        // 如果有輸入產品名稱作為搜尋條件時
        if (!string.IsNullOrWhiteSpace(viewModel.ProductName))
        { products = products.Where(p => p.Name.Contains(viewModel.ProductName)); }

        // 如果有輸入生產工廠作為搜尋條件時
        if (!string.IsNullOrWhiteSpace(viewModel.Manufactory))
        { products = products.Where(p => p.Manufactory.Contains(viewModel.Manufactory)); }

        // 回傳搜尋結果
        viewModel.Products= products.Include(p => p.ProductType)
                                    .OrderBy(p => p.ProductId)
                                    .ToPagedList(viewModel.Page > 0 ? viewModel.Page - 1 : 0, PageSize);

        return View(viewModel);
    }
}

 

** 注意搜尋條件必需排序,否則會報錯

image

 

最後在View加入Pager控制項

image

 

其中因Pager切換頁面都是以GET方式傳遞資料,但一般而言都會比較傾向使用POST方式處理,因此可透過JavaScript來Binding切換頁面連結,點擊時將頁碼(Page)資訊加入Form表單中與搜尋條件一併POST到後端,達到搜尋+換頁的功能,實作請參考以下代碼。


    $(function () {

        // Fields
        var _pageLinkers = $(".pager> a");

        // Binding click event
        _pageLinkers.each(function (i, item) {
            var page = getParameterByName($(item).attr('href'), 'page')
            $(item).attr('href', '#').click(function () { postPage(page); });
        });

    });


    function getParameterByName(url,name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(url);
        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }

    function postPage(page) {
        var targetFormID = '#ProductList';
        if ($(targetFormID).size() > 0) {
            $('<input>')
                .attr({ type: 'hidden', id: 'page', name: 'page', value: page })
                .appendTo($(targetFormID));
            $(targetFormID).submit();
        }
    };

</script>

 

完整View代碼如下

<h3>Search Product</h3>

@using (Html.BeginForm("Index", "Product", FormMethod.Post, new { id = "ProductList" }))  
{
    <table class="table-condensed">
        <tr>
            <td>名稱</td> <td> @Html.EditorFor(model => model.ProductName)</td>
            <td>工廠</td> <td> @Html.EditorFor(model => model.Manufactory) </td>
            <td> <input type="submit" value="Search" /> </td>
        </tr>
    </table>

    if (Model.Products != null && Model.Products.Count() > 0)
    {
    
    <table class="table">
        <tr>
            <th>
                @*@Html.DisplayNameFor(model => model.Name)*@
                @Html.DisplayNameFor(model => model.Products.First().Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Description)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Manufactory)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().Price)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Products.First().ProductType.Name)
            </th>
            <th></th>
        </tr>

    @foreach (var item in Model.Products) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Manufactory)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ProductType.Name)
            </td>
            <td>
                @Html.ActionLink("加入購物車", "AddInCart", new { id = item.ProductId })
            </td>
        </tr>
    }

    </table>

    <div class="pager">
        @Html.Pager(Model.Products.PageSize, Model.Products.PageNumber, Model.Products.TotalItemCount)  
        Displaying @Model.Products.ItemStart - @Model.Products.ItemEnd of @Model.Products.TotalItemCount item(s)
    </div>

    }
}


@section Scripts {
    
@Scripts.Render("~/bundles/jqueryval")

<script>

    $(function () {

        // Fields
        var _pageLinkers = $(".pager> a");

        // Binding click event
        _pageLinkers.each(function (i, item) {
            var page = getParameterByName($(item).attr('href'), 'page')
            $(item).attr('href', '#').click(function () { postPage(page); });
        });

    });


    function postPage(page) {
        var targetFormID = '#ProductList';
        if ($(targetFormID).size() > 0) {
            $('<input>')
                .attr({ type: 'hidden', id: 'page', name: 'page', value: page })
                .appendTo($(targetFormID));
            $(targetFormID).submit();
        }
    };

    function getParameterByName(url,name) {
        name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
        var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
            results = regex.exec(url);
        return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
    }

</script>

}

 

來看一下成果 (Pager出現囉)

image

 

切換頁面時,確實是使用POST方式進行,且正確顯示第2頁資料

image

 

 

如果嫌棄Pager外觀可將開發者預設的CSS語法加入~/Content/Site.css進行調整

 

.pager
{
    margin: 8px 3px;
    padding: 3px;
}
 
.pager .disabled
{
    border: 1px solid #ddd;
    color: #999;
    margin-top: 4px;
    padding: 3px;
    text-align: center;
}
 
.pager .current
{
    background-color: #6ea9bf;
    border: 1px solid #6e99aa;
    color: #fff;
    font-weight: bold;
    margin-top: 4px;
    padding: 3px 5px;
    text-align: center;
}
 
.pager span, .pager a
{
    margin: 4px 3px;
}
 
.pager a
{
    border: 1px solid #aaa;
    padding: 3px 5px;
    text-align: center;
    text-decoration: none;
}

 

調整後是不是漂亮多了

image

 

 

結論

 

透過本篇文章的介紹可以發現,使用MvcPaging來實作分頁功能其實對程式的衝擊(異動)並不大,簡單來說只需要在ViewModel中額外加入Page屬性來傳遞頁碼資訊,再將資料類型由IEnumerable改為IPagedList,最後就在View中加上Pager控制項即可,真的是相當的人性化,如果有此需求的朋友不妨體驗看看。另外如果有Ajax換頁需求的朋友可以參考前輩ASP.NET MVC 資料分頁 - 使用 PagedList.Mvc系列文,裡面有非常詳細的教學說明喔。

 

 

參考資料

 

https://github.com/martijnboland/MvcPaging

http://kevintsengtw.blogspot.tw/2012/07/aspnet-mvc-mvcpaging-20-part1form.html#.VJzFhV4A8


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !