razorpage

精通 .Net Razor Page 表格分頁:從後端刷新到 AJAX 動態更新的實戰指南

【Maxine】趙家瑜 (昕力 DTD) 2025/09/11 14:29:32
43

在現代 Web 開發中,資料分頁是提升使用者體驗與網站效能的關鍵功能。

當表格需要處理大量資料時,一次性載入所有內容不僅會拖慢載入速度,也會讓使用者難以瀏覽。

本篇文章將以 KaiAdmin 後台管理範本 的 免費UI 為例,帶您一步步在 ASP.NET Core Razor Pages 中,從零到有打造一個功能完整、可重用的表格分頁元件,並提供「傳統後端分頁」與「AJAX 無刷新分頁」兩種實戰方法。

 

第一步:打造可重用的分頁核心元件

 

要實現一個好的分頁功能,首先需要建立一組可重用的核心元件。這包含了顯示分頁 UI 的 Razor 元件、儲存分頁狀態的模型,以及處理分頁邏輯的輔助類別。

1. 分頁資訊模型 (PagingInfoModel.cs)

此類別是分頁功能的核心資料結構,用於儲存和計算所有與分頁相關的狀態,例如總頁數、目前頁碼、項目總數等。

public class PagingInfoModel
{
    /// <summary>
    /// 當前頁碼,預設第一頁
    /// </summary>
    public int CurrentPage { get; set; }

    /// <summary>
    /// 每頁顯示的筆數
    /// </summary>
    public int PageSize { get; set; }

    /// <summary>
    /// 總共筆數
    /// </summary>
    public int TotalItems { get; set; }

    /// <summary>
    /// 總共頁數 (自動計算)
    /// </summary>
    public int TotalPages => (int)Math.Ceiling(decimal.Divide(TotalItems, PageSize));

    /// <summary>
    /// 當前頁面的起始項目索引
    /// </summary>
    public int StartItem => (CurrentPage - 1) * PageSize + 1;

    /// <summary>
    /// 當前頁面的結束項目索引
    /// </summary>
    public int EndItem => Math.Min(CurrentPage * PageSize, TotalItems);
}

2. 分頁邏輯輔助類別 (PagingHelper.cs)

 

這個輔助類別將分頁的核心邏輯封裝起來,包含根據當前頁碼篩選資料,以及產生分頁資訊模型。這讓我們的 PageModel 程式碼更乾淨、更專注於業務邏輯。

建議位置: 專案中新建一個 HelpersServices 資料夾。

/// <summary>
/// 處理分頁相關邏輯的輔助類別
/// </summary>
public class PagingHelper
{
    /// <summary>
    /// 對 IQueryable 資料源執行分頁查詢
    /// </summary>
    /// <typeparam name="TEntity">資料實體類型</typeparam>
    /// <param name="data">查詢資料來源</param>
    /// <param name="currentPage">目前頁數</param>
    /// <param name="pageSize">每頁最大筆數</param>
    /// <returns>分頁後的 IQueryable 結果</returns>
    public IQueryable<TEntity> PaginationQuery<TEntity>(IQueryable<TEntity> data, int currentPage, int? pageSize)
    {
        if (pageSize == null || pageSize.GetValueOrDefault() < 1)
        {
            return data;
        }
        // 使用 Skip 和 Take 實現高效的資料庫端分頁
        return data.Skip((currentPage - 1) * pageSize.Value).Take(pageSize.Value);
    }

    /// <summary>
    /// 產生分頁資訊模型
    /// </summary>
    /// <param name="currentPage">目前頁數</param>
    /// <param name="pageSize">每頁最大筆數</param>
    /// <param name="totalItems">總筆數</param>
    /// <returns>PagingInfoModel 實例</returns>
    public PagingInfoModel GetPagingInfo(int currentPage, int pageSize, int totalItems)
    {
        return new PagingInfoModel
        {
            CurrentPage = currentPage,
            PageSize = pageSize,
            TotalItems = totalItems
        };
    }
}

提示: 為了方便在 PageModel 中使用 PagingHelper,建議透過 依賴注入 (Dependency Injection) 來註冊它。在 Program.cs 中加入: builder.Services.AddScoped<PagingHelper>();

 

3. 分頁 UI 元件 (_Pagination.cshtml)

 

這是一個共用的 Partial View,它接收 PagingInfoModel 作為模型,並根據模型中的數據動態產生分頁控制項的 HTML,包含上一頁、下一頁及頁碼按鈕。

位置: Pages/Shared 資料夾。

 

@using CDFH_CHM.Models
@*
    共用分頁 UI 元件
    接收 PagingInfoModel,動態產生分頁按鈕與資訊。
*@
@model PagingInfoModel

<div class="col-sm-12 col-md-5">
    <div class="dataTables_info" id="basic-datatables_info" role="status" aria-live="polite">
        Showing @Model.StartItem to @Model.EndItem of @Model.TotalItems entries
    </div>
</div>
<div class="col-sm-12 col-md-7">
    <div class="dataTables_paginate paging_simple_numbers">
        <ul class="pagination">
            <li class="paginate_button page-item previous @(Model.CurrentPage == 1 ? "disabled" : "")">
                <a href="?pageIndex=@(Model.CurrentPage - 1)" aria-controls="basic-datatables" tabindex="0" class="page-link">Previous</a>
            </li>
            @for (int i = 1; i <= Model.TotalPages; i++)
            {
                <li class="paginate_button page-item @(Model.CurrentPage == i ? "active" : "")">
                    <a href="?pageIndex=@i" aria-controls="basic-datatables" tabindex="0" class="page-link">@i</a>
                </li>
            }
            <li class="paginate_button page-item next @(Model.CurrentPage == Model.TotalPages ? "disabled" : "")">
                <a href="?pageIndex=@(Model.CurrentPage + 1)" aria-controls="basic-datatables" tabindex="0" class="page-link">Next</a>
            </li>
        </ul>
    </div>
</div>

 

第二步:整合分頁元件至 Razor Page

 

核心元件建立完成後,我們來看看如何在實際頁面中應用它們。

 

方法一:傳統伺服器端分頁 (頁面刷新)

 

這是最直接且簡單的實現方式。使用者每次點擊分頁按鈕,都會向伺服器發送一個完整的請求,伺服器處理後回傳整個新頁面。

 

1. 後端邏輯 (PageModel)

 

在 PageModel 中,我們注入 PagingHelper,並在 OnGet 方法中接收 pageIndex 參數。根據此參數,我們從資料庫中取得對應頁數的資料,並設定好分頁資訊。

public class IndexModel : PageModel
{
    private readonly PagingHelper _pagingHelper;
    private const int _pageSize = 10; // 每頁顯示筆數,可依需求調整

    public IndexModel(PagingHelper pagingHelper)
    {
        _pagingHelper = pagingHelper;
    }

    public PagingInfoModel PageInfo { get; set; }
    public List<YourDataModel> Items { get; set; } // 請替換為您的資料模型

    // 從 URL 查詢字串 ?pageIndex=... 綁定頁碼
    public void OnGet(int pageIndex = 1) 
    {
        // 1. 假設這是您從資料庫取得的完整 IQueryable 資料源
        var yourDataSource = GetYourDataFromDatabase().AsQueryable(); 
        int totalItems = yourDataSource.Count();

        // 2. 使用 PagingHelper 產生分頁資訊
        PageInfo = _pagingHelper.GetPagingInfo(pageIndex, _pageSize, totalItems);
        
        // 3. 使用 PagingHelper 取得當前頁次的資料
        Items = _pagingHelper.PaginationQuery(yourDataSource, PageInfo.CurrentPage, PageInfo.PageSize).ToList();
    }
}

 

2. 前端頁面 (Page.cshtml)

 

在您的表格 HTML 結構下方,使用 PartialAsync 引入 _Pagination 元件,並將 PageModel 中的 PageInfo 物件傳入。

@page
@model IndexModel

<table class="table table-bordered">
    ...
</table>
<div class="row">
    @await Html.PartialAsync("_Pagination", Model.PageInfo)
</div>

效果預覽:

 

方法二:AJAX 動態分頁 (無刷新更新)

 

這種方法提供更流暢的使用者體驗。使用者點擊分頁按鈕時,頁面不會重新整理。取而代之的是,一段 JavaScript 程式碼會非同步地向伺服器請求新一頁的資料,然後僅更新表格和分頁控制項的區塊。

 

1. 調整 _Pagination.cshtml (for AJAX)

 

我們需要移除 <a> 標籤的 href 屬性,改用 data- 屬性來儲存頁碼。點擊事件將由 JavaScript 捕捉。

...
<li class="paginate_button page-item previous @(Model.CurrentPage == 1 ? "disabled" : "")">
    <a class="page-link" data-page="@(Model.CurrentPage - 1)">Previous</a>
</li>
@for (int i = 1; i <= Model.TotalPages; i++)
{
    <li class="paginate_button page-item @(Model.CurrentPage == i ? "active" : "")">
        <a class="page-link" data-page="@i">@i</a>
    </li>
}
<li class="paginate_button page-item next @(Model.CurrentPage == Model.TotalPages ? "disabled" : "")">
    <a class="page-link" data-page="@(Model.CurrentPage + 1)">Next</a>
</li>
...

 

2. 調整 PageModel (cshtml.cs)

 

PageModel 的 OnGet 方法基本不變,但返回的結果需要根據請求的類型來決定。如果是 AJAX 請求,我們通常會返回一個 Partial View;如果是一般請求,則返回整個 Page。為了簡化範例,這裡的 OnGet 會在 AJAX 請求時,重新渲染整個頁面內容,再由前端 JS 擷取所需部分。

// PageModel 程式碼與方法一相同,無需變更。
// Razor Pages 的架構會自動處理 AJAX GET 請求並回傳完整的 HTML,
// 接著由我們的 JavaScript 腳本從中提取需要更新的部分。

 

3. 調整主頁面 (Page.cshtml)

 

為表格和分頁的容器 <div> 加上 id,以便 JavaScript 能夠精準地找到並替換它們的內容。同時,我們透過 data-route 屬性將當前頁面的路徑傳遞給 JavaScript。

@page "{handler?}"
@model IndexModel

@{
    // 取得當前頁面的路徑,供 AJAX 使用
    var routePath = Url.Page(Model.PageContext.ActionDescriptor.DisplayName);
}

<div id="table-wrapper">
    </div>

<div class="row" id="pagination-container" data-route="@routePath">
     @await Html.PartialAsync("_Pagination", Model.PageInfo)
</div>

@section Scripts {
    <script src="~/js/pagination.js"></script>
}

 

4. 建立 pagination.js

 

這是實現 AJAX 更新的關鍵。我們監聽分頁按鈕的點擊事件,阻止其預設行為,然後讀取 data-pagedata-route 屬性,發送 GET 請求。成功後,使用 jQuery 從回傳的 HTML 中解析出新的表格和分頁內容,替換掉舊的內容。

建議位置: wwwroot/js/pagination.js

$(document).ready(function () {
    // 使用事件委派,監聽 #pagination-container 內部 .page-link 的點擊事件
    $(document).on("click", "#pagination-container .page-link", function (event) {
        event.preventDefault(); // 防止 <a> 標籤的預設跳轉行為

        // 取得目標頁碼和請求路徑
        var page = $(this).data("page");
        var routePath = $('#pagination-container').data('route');
        
        // 如果點擊的是無效按鈕 (例如已在第一頁點擊 "Previous"),則不執行任何操作
        if (!page) {
            return;
        }

        // 發送 AJAX GET 請求
        $.ajax({
            url: routePath,
            type: 'GET',
            data: { pageIndex: page },
            success: function (result) {
                // 從回傳的完整 HTML (result) 中,找到並提取新內容
                const newTable = $(result).find("#table-wrapper").html();
                const newPagination = $(result).find("#pagination-container").html();

                // 將新內容更新到頁面上的對應位置
                $("#table-wrapper").html(newTable);
                $("#pagination-container").html(newPagination);
            },
            error: function () {
                // 簡易的錯誤處理
                alert("An error occurred while loading data.");
            }
        });
    });
});

效果預覽:

 

結論與建議

 

我們成功地介紹了兩種在 .Net Razor Pages 中建立表格分頁的方法:

  1. 傳統伺服器端分頁:

    • 優點: 實現簡單、直觀,後端邏輯集中。

    • 缺點: 每次換頁都需要重新載入整個頁面,使用者體驗較差,伺服器負擔稍重。

    • 適用場景: 內部管理系統、對使用者體驗要求不高的快速開發專案。

  2. AJAX 動態分頁:

    • 優點: 無刷新更新,提供如絲般滑順的使用者體驗,只傳輸必要數據,減輕伺服器壓力。

    • 缺點: 需要額外編寫 JavaScript,前後端邏輯稍微分離,複雜度略高。

    • 適用場景: 面向外部使用者的網站、追求極致體驗的單頁應用 (SPA) 或後台管理系統。

透過將分頁邏輯封裝成可重用的元件 (PagingInfoModel, PagingHelper, _Pagination.cshtml),您的專案將變得更加模組化且易於維護。您可以根據專案的具體需求,選擇最適合的分頁實現方式。

【Maxine】趙家瑜 (昕力 DTD)