精通 .Net Razor Page 表格分頁:從後端刷新到 AJAX 動態更新的實戰指南
在現代 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 程式碼更乾淨、更專注於業務邏輯。
建議位置: 專案中新建一個 Helpers
或 Services
資料夾。
/// <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-page
和 data-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 中建立表格分頁的方法:
-
傳統伺服器端分頁:
-
優點: 實現簡單、直觀,後端邏輯集中。
-
缺點: 每次換頁都需要重新載入整個頁面,使用者體驗較差,伺服器負擔稍重。
-
適用場景: 內部管理系統、對使用者體驗要求不高的快速開發專案。
-
-
AJAX 動態分頁:
-
優點: 無刷新更新,提供如絲般滑順的使用者體驗,只傳輸必要數據,減輕伺服器壓力。
-
缺點: 需要額外編寫 JavaScript,前後端邏輯稍微分離,複雜度略高。
-
適用場景: 面向外部使用者的網站、追求極致體驗的單頁應用 (SPA) 或後台管理系統。
-
透過將分頁邏輯封裝成可重用的元件 (PagingInfoModel
, PagingHelper
, _Pagination.cshtml
),您的專案將變得更加模組化且易於維護。您可以根據專案的具體需求,選擇最適合的分頁實現方式。