傳統 ASP.NET WebForms 分頁導致整頁回傳,體驗不佳。結合 jQuery Pagination Plugin 與 JSON 資料交換,可實現流暢的無刷新分頁。本文提供前後端完整程式碼,包含分頁控件初始化、AJAX 請求、JSON 序列化、資料表動態渲染。
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SqlPage.aspx.cs" Inherits="SqlPage" %>
<!DOCTYPE html>
<html>
<head>
<title>無刷新分頁示範</title>
<link href="css/pagination.css" rel="stylesheet" />
<style>
#tblData { width: 100%; border-collapse: collapse; }
#tblData th, #tblData td { border: 1px solid #ddd; padding: 8px; text-align: center; }
#tblData th { background: #f5f5f5; }
.pagination { text-align: center; margin: 20px 0; }
.pagination a, .pagination span { display: inline-block; padding: 6px 12px; margin: 0 2px; border: 1px solid #ddd; color: #007bff; text-decoration: none; }
.pagination .current { background: #007bff; color: #fff; border-color: #007bff; }
.pagination .disabled { color: #999; pointer-events: none; }
</style>
</head>
<body>
<form id="form1" runat="server">
<table id="tblData">
<thead>
<tr><th>NewsID</th><th>Title</th><th>Category</th><th>Author</th><th>UpdateTime</th></tr>
</thead>
<tbody><!-- 資料由 JS 動態填充 --></tbody>
</table>
<div id="Pagination" class="pagination"></div>
</form>
<script src="js/jquery-1.12.4.min.js"></script>
<script src="js/jquery.pagination.js"></script>
<script>
var pageSize = 20;
$(function() {
// 初始化分頁控件 (總筆數由後端輸出 <%=pageCount%>)
$("#Pagination").pagination(<%=pageCount %>, {
callback: PageCallback,
prev_text: '上一頁',
next_text: '下一頁',
items_per_page: pageSize,
num_display_entries: 5,
current_page: 0,
num_edge_entries: 1,
link_to: 'javascript:void(0)' // 防止跳轉
});
});
function PageCallback(pageIndex, jq) {
LoadPageData(pageIndex + 1); // 插件從 0 起算,後端從 1 起算
}
function LoadPageData(pageIndex) {
$.ajax({
type: "POST",
dataType: "json", // 直接解析 JSON
url: 'SqlPage.aspx',
data: { pageIndex: pageIndex, pageSize: pageSize },
beforeSend: function() {
$("#tblData tbody").html('載入中... ');
},
success: function(data) {
if (data && data.News && data.News.length > 0) {
RenderTable(data.News);
} else {
$("#tblData tbody").html('無資料 ');
}
},
error: function(xhr, status, error) {
$("#tblData tbody").html('載入失敗:' + error + ' ');
}
});
}
function RenderTable(newsList) {
var html = [];
$.each(newsList, function(i, news) {
html.push('');
html.push('' + news.NewsID + ' ');
html.push('' + htmlEncode(news.Title) + ' ');
html.push('' + htmlEncode(news.SmallClassName) + ' ');
html.push('' + htmlEncode(news.Author) + ' ');
html.push('' + news.Updatetime + ' ');
html.push(' ');
});
$("#tblData tbody").html(html.join(''));
}
// 簡易 HTML 編碼防 XSS
function htmlEncode(str) {
return $('').text(str).html();
}
</script>
</body>
</html>
using System;
using System.Data;
using System.Web.UI;
public partial class SqlPage : Page
{
public int pageCount = 0; // 供前端 <%=pageCount%> 使用
private const string ConnString = "server=192.168.1.91;database=ReportDB;uid=sa;pwd=123456";
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// 首次載入:只計算總筆數,渲染分頁控件
pageCount = GetTotalCount();
}
else
{
// AJAX 回傳:檢查分頁參數
if (Request["pageIndex"] != null && Request["pageSize"] != null)
{
int pageSize = Convert.ToInt32(Request["pageSize"]);
int pageIndex = Convert.ToInt32(Request["pageIndex"]);
string json = GetPageDataJson(pageSize, pageIndex);
Response.Clear();
Response.ContentType = "application/json; charset=utf-8";
Response.Write(json);
Response.End(); // 關鍵:避免輸出頁面 HTML
}
}
}
private int GetTotalCount()
{
using (var conn = new System.Data.SqlClient.SqlConnection(ConnString))
using (var cmd = new System.Data.SqlClient.SqlCommand("SELECT COUNT(*) FROM News", conn))
{
conn.Open();
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
private string GetPageDataJson(int pageSize, int pageIndex)
{
// SQL Server 2012+ OFFSET/FETCH 分頁 (效能最佳)
string sql = @"
SELECT NewsID, Title, SmallClassName, Author, Updatetime
FROM News
ORDER BY NewsID DESC
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY";
using (var conn = new System.Data.SqlClient.SqlConnection(ConnString))
using (var cmd = new System.Data.SqlClient.SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize);
cmd.Parameters.AddWithValue("@PageSize", pageSize);
conn.Open();
using (var reader = cmd.ExecuteReader())
{
return ConvertJson.ToJson(reader, "News"); // 使用前文 ConvertJson 工具類
}
}
}
}
// 參考文章 54639 的 ConvertJson 類別
public static string ToJson(DbDataReader reader, string jsonName)
{
var sb = new StringBuilder();
sb.Append("{\"" + jsonName + "\":[");
bool first = true;
while (reader.Read())
{
if (!first) sb.Append(",");
sb.Append("{");
for (int i = 0; i < reader.FieldCount; i++)
{
string name = reader.GetName(i);
Type type = reader.GetFieldType(i);
object val = reader[i];
sb.Append("\"" + name + "\":" + FormatValue(val, type));
if (i < reader.FieldCount - 1) sb.Append(",");
}
sb.Append("}");
first = false;
}
reader.Close();
sb.Append("]}");
return sb.ToString();
}
| SQL Server 版本 | 分頁語法 | 效能 |
|---|---|---|
| 2000/2005 | NOT IN / ROW_NUMBER() 子查詢 | 較差,大數據深分頁慢 |
| 2008/2008 R2 | ROW_NUMBER() OVER() + CTE | 中等 |
| 2012+ | OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY | 最佳,原生支援 |
| MySQL/PostgreSQL | LIMIT @PageSize OFFSET @Offset | 最佳 |
// Controller
[HttpGet]
public async Task List(int page = 1, int pageSize = 20)
{
var query = _context.News.OrderByDescending(n => n.NewsID);
var total = await query.CountAsync();
var items = await query.Skip((page-1)*pageSize).Take(pageSize).ToListAsync();
return Json(new { total, items, page, pageSize });
}
// 前端 (原生 Fetch)
async function loadPage(page) {
const res = await fetch(`/News/List?page=${page}&pageSize=20`);
const data = await res.json();
renderTable(data.items);
renderPager(data.total, data.pageSize, data.page);
}
jQuery Pagination + JSON 無刷新分頁的核心架構:前端分頁控件管理 UI 狀態 → AJAX 請求帶分頁參數 → 後端參數化查詢當頁資料 → JSON 序列化回傳 → 前端動態渲染表格。關鍵點:`Response.End()` 截斷頁面輸出、參數化 SQL 防注入、OFFSET/FETCH 高效分頁、JSON 日期格式標準化。新專案建議採用原生 Fetch + ASP.NET Core Web API 架構。