动态HTML表格自定义分页异常:点击下一页所有行消失求助
问题描述
动态构建HTML表格实现自定义分页时遇到异常:结果集共约30000条记录,表格初始加载可正常显示10条数据,但点击“下一页”按钮后所有行消失。
HTML标记
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="AzureAutomation.Default" %> <asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> <asp:ScriptManager runat="server" EnablePageMethods="true"> <Scripts> <%--Framework Scripts--%> <asp:ScriptReference Name="MsAjaxBundle" /> <asp:ScriptReference Name="jquery" /> <asp:ScriptReference Name="bootstrap" /> </Scripts> </asp:ScriptManager> <div class="jumbotron"> <h1>Welcome to the Microsoft and Proofpoint automation tool</h1> <p class="lead">Reset Session Tokens, Reset MFA Registration, and Reset / Lockdown Accounts, all at the click of a button</p> </div> <div class="jumbotron"> <div id="userTablePlaceholder" clientidmode="Static" runat="server"></div> <button id="prevPage" clientidmode="Static" runat="server">Previous</button> <button id="nextPage" clientidmode="Static" runat="server">Next</button> </div> <div class="row"> <div class="col-md-4"> <h2>Reset Session Token</h2> <p>This will reset the session token of the selected user.</p> <p> <asp:Button ID="btnResetSessionToken" CssClass="btn btn-default" runat="server" Text="Reset Session Token" OnClick="btnResetSessionToken_Click" /> </p> </div> <div class="col-md-4"> <h2>Reset MFA Registration</h2> <p>This will reset the MFA registration of the selected user.</p> <p> <asp:Button ID="btnResetMFARegistration" CssClass="btn btn-default" runat="server" Text="Reset MFA Registration" OnClick="btnResetMFARegistration_Click" /> </p> </div> <div class="col-md-4"> <h2>Reset Account</h2> <p>This will reset the account status of the selected user.</p> <p> <asp:Button ID="btnResetAccount" CssClass="btn btn-default" runat="server" Text="Reset Account" OnClick="btnResetAccount_Click" /> </p> </div> </div> <div class="row"> <div class="col-md-4"> <h2>Lockdown Account</h2> <p>This will completely lockdown the account of the selected user.</p> <p> <asp:Button ID="btnLockdownAccount" CssClass="btn btn-default" runat="server" Text="Lockdown Account" OnClick="btnLockdownAccount_Click" /> </p> </div> </div> <script> document.addEventListener("DOMContentLoaded", function () { // Initial page index and page size var pageIndex = 0; var pageSize = 10; var totalRecords = <%= this.TotalRecords %>; // Assuming 'TotalRecords' is the total count returned from the server-side method var maxPageIndex = Math.ceil(totalRecords / pageSize) - 1; // Initial page display showPage(pageIndex, pageSize); updateButtonState(); // Handle page navigation document.getElementById("nextPage").addEventListener("click", nextPageHandler); document.getElementById("prevPage").addEventListener("click", prevPageHandler); // Function to show/hide rows based on current page function showPage(pageIndex, pageSize) { var rows = document.querySelectorAll("#userTable tbody tr"); var start = pageIndex * pageSize; var end = Math.min(start + pageSize, rows.length); // Ensure end does not exceed the total number of rows for (var i = 0; i < rows.length; i++) { rows[i].style.display = (i >= start && i < end) ? "" : "none"; } } // Function to handle next page button click function nextPageHandler() { event.preventDefault(); if (pageIndex < maxPageIndex) { pageIndex++; showPage(pageIndex, pageSize); updateButtonState(); } } // Function to handle previous page button click function prevPageHandler() { event.preventDefault(); if (pageIndex > 0) { pageIndex--; showPage(pageIndex, pageSize); updateButtonState(); } } // Function to update button state function updateButtonState() { document.getElementById("prevPage").disabled = (pageIndex === 0); document.getElementById("nextPage").disabled = (pageIndex === maxPageIndex); } }); </script> </asp:Content>
后台代码
using AzureAutomation.API; using AzureAutomation.Helpers; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Configuration; using System.Data; using System.DirectoryServices; using System.Linq; using System.Text; using System.Web.Configuration; using System.Web.Script.Serialization; using System.Web.Services; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using Logging; using Logging.NLog; namespace AzureAutomation { public partial class Default : Page { public int TotalRecords { get; set; } protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { var (searchResults, totalRecords) = UsersController.LoadActiveDirectoryAccounts(); string userTableHtml = GenerateUserTable(searchResults, pageIndex: 0, pageSize: 10); userTablePlaceholder.InnerHtml = userTableHtml; // userTablePlaceholder is a placeholder for the HTML table in your Default.aspx TotalRecords = totalRecords; } } private string GenerateUserTable(List<SearchResult> searchResults, int pageIndex, int pageSize) { StringBuilder htmlBuilder = new StringBuilder(); // Start the table htmlBuilder.Append("<table id='userTable' clientidmode='Static' class='table table-bordered table-striped'>"); htmlBuilder.Append("<thead><tr><th>User Principal Name</th><th>Display Name</th><th>SamAccount Name</th></tr></thead>"); htmlBuilder.Append("<tbody>"); // Calculate the start and end indexes for the current page int startIndex = pageIndex * pageSize; int endIndex = Math.Min(startIndex + pageSize, searchResults.Count); // Loop through the subset of searchResults for the current page and add rows to the table for (int i = startIndex; i < endIndex; i++) { var result = searchResults[i]; htmlBuilder.Append("<tr>"); htmlBuilder.Append("<td>").Append(result.Properties["userPrincipalName"][0]).Append("</td>"); htmlBuilder.Append("<td>").Append(result.Properties["DisplayName"][0]).Append("</td>"); htmlBuilder.Append("<td>").Append(result.Properties["samaccountname"][0]).Append("</td>"); htmlBuilder.Append("</tr>"); } // End the table htmlBuilder.Append("</tbody></table>"); return htmlBuilder.ToString(); } } }
数据检索代码
public static (List<SearchResult> searchResults, int totalRecords) LoadActiveDirectoryAccounts() { List<SearchResult> allResults = new List<SearchResult>(); var OUSplitList = OuList.Split('|').ToList(); int totalRecords = 0; // Initialize the total count variable try { foreach (var OU in OUSplitList) { _logger.Info($"Loading AD accounts from {OU}"); using (var dirEntry = GetDirectoryEntry(OU)) { string[] loadProps = { "DisplayName", "samaccountname", "userPrincipalName" }; using (var dirSearch = new DirectorySearcher(dirEntry, "(&(objectClass=user)(objectCategory=person))", loadProps)) { dirSearch.PageSize = 1000; dirSearch.Sort = new SortOption("samaccountname", SortDirection.Ascending); var results = SafeFindAll(dirSearch); allResults.AddRange(results); totalRecords = allResults.Count; // Increment the total count by the count of results } } _logger.Info($"Accounts loaded - {allResults.Count} "); } } catch (Exception ex) { throw new Exception(); } return (allResults, totalRecords); // Return both the list of search results and the total count } private static DirectoryEntry GetDirectoryEntry(string domain) { return new DirectoryEntry("LDAP://" + domain, Globals.IamUser, Globals.IamPassword); } public static IEnumerable<SearchResult> SafeFindAll(DirectorySearcher searcher) { using (SearchResultCollection results = searcher.FindAll()) { foreach (SearchResult usr in results) { yield return usr; } } // SearchResultCollection will be disposed here }
问题原因
核心矛盾在于前端分页逻辑和后台渲染逻辑不匹配:
- 后台
GenerateUserTable方法在页面首次加载时,只渲染了第一页的10条数据,页面表格的tbody中仅有10个<tr>元素。 - 前端
showPage函数中,rows.length的值为10;点击下一页后pageIndex变为1,计算出start = 1*10 = 10、end = Math.min(20,10) =10。此时遍历所有行(索引0-9),没有任何行满足i >=10 && i <10的条件,所有行都被设置为display:none,导致表格内容消失。
修复方案
提供两种可行方案,根据性能需求选择:
方案1:客户端全量分页(简单但大数据量下性能受限)
修改后台代码,一次性渲染所有记录,前端通过控制行的显示/隐藏实现分页:
- 调整后台
Page_Load方法:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { var (searchResults, totalRecords) = UsersController.LoadActiveDirectoryAccounts(); // 传入pageIndex=-1,让GenerateUserTable渲染所有行 string userTableHtml = GenerateUserTable(searchResults, pageIndex: -1, pageSize: 0); userTablePlaceholder.InnerHtml = userTableHtml; TotalRecords = totalRecords; } }
- 修改后台
GenerateUserTable方法:
private string GenerateUserTable(List<SearchResult> searchResults, int pageIndex, int pageSize) { StringBuilder htmlBuilder = new StringBuilder(); htmlBuilder.Append("<table id='userTable' clientidmode='Static' class='table table-bordered table-striped'>"); htmlBuilder.Append("<thead><tr><th>User Principal Name</th><th>Display Name</th><th>SamAccount Name</th></tr></thead>"); htmlBuilder.Append("<tbody>"); // 如果pageIndex为-1,渲染所有行 IEnumerable<SearchResult> rowsToRender = pageIndex == -1 ? searchResults : searchResults.Skip(pageIndex * pageSize).Take(pageSize); foreach (var result in rowsToRender) { htmlBuilder.Append("<tr>"); htmlBuilder.Append("<td>").Append(result.Properties["userPrincipalName"][0]).Append("</td>"); htmlBuilder.Append("<td>").Append(result.Properties["DisplayName"][0]).Append("</td>"); htmlBuilder.Append("<td>").Append(result.Properties["samaccountname"][0]).Append("</td>"); htmlBuilder.Append("</tr>"); } htmlBuilder.Append("</tbody></table>"); return htmlBuilder.ToString(); }
注意:30000条记录一次性渲染会生成大量DOM元素,可能导致页面加载缓慢、卡顿,仅适合小数据集场景。
方案2:AJAX异步分页(推荐,适合大数据量)
通过AJAX请求后台获取指定页码的数据,动态更新表格内容,避免一次性加载大量DOM:
- 后台添加WebMethod用于获取指定页的数据:
[WebMethod] public static string GetUserPage(int pageIndex, int pageSize) { var (allResults, totalRecords) = UsersController.LoadActiveDirectoryAccounts(); var pageResults = allResults.Skip(pageIndex * pageSize).Take(pageSize).ToList(); // 将数据序列化为JSON,前端解析后生成表格行 var serializer = new JavaScriptSerializer(); return serializer.Serialize(new { Rows = pageResults.Select(r => new { UserPrincipalName = r.Properties["userPrincipalName"][0].ToString(), DisplayName = r.Properties["DisplayName"][0].ToString(), SamAccountName = r.Properties["samaccountname"][0].ToString() }), TotalRecords = totalRecords }); }
- 修改前端JavaScript代码,改用AJAX加载分页数据:
document.addEventListener("DOMContentLoaded", function () { var pageIndex = 0; var pageSize = 10; var totalRecords = 0; var maxPageIndex = 0; // 初始加载第一页 loadPage(pageIndex, pageSize); // 绑定按钮事件 document.getElementById("nextPage").addEventListener("click", nextPageHandler); document.getElementById("prevPage").addEventListener("click", prevPageHandler); // 加载指定页数据 function loadPage(pageIndex, pageSize) { $.ajax({ type: "POST", url: "Default.aspx/GetUserPage", data: JSON.stringify({ pageIndex: pageIndex, pageSize: pageSize }), contentType: "application/json; charset=utf-8", dataType: "json", success: function (response) { var data = response.d; totalRecords = data.TotalRecords; maxPageIndex = Math.ceil(totalRecords / pageSize) - 1; // 生成表格行 var tbody = document.querySelector("#userTable tbody"); tbody.innerHTML = ""; data.Rows.forEach(function (row) { var tr = document.createElement("tr"); tr.innerHTML = `<td>${row.UserPrincipalName}</td><td>${row.DisplayName}</td><td>${row.SamAccountName}</td>`; tbody.appendChild(tr); }); updateButtonState(); }, error: function (xhr, status, error) { console.error("加载数据失败:", error); } }); } function nextPageHandler() { event.preventDefault(); if (pageIndex < maxPageIndex) { pageIndex++; loadPage(pageIndex, pageSize); } } function prevPageHandler() { event.preventDefault(); if (pageIndex > 0) { pageIndex--; loadPage(pageIndex, pageSize); } } function updateButtonState() { document.getElementById("prevPage").disabled = (pageIndex === 0); document.getElementById("nextPage").disabled = (pageIndex === maxPageIndex); } });
- 调整后台
Page_Load方法,初始化空表格结构:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { // 初始化空表格结构,避免前端找不到userTable元素 string emptyTableHtml = @"<table id='userTable' clientidmode='Static' class='table table-bordered table-striped'> <thead><tr><th>User Principal Name</th><th>Display Name</th><th>SamAccount Name</th></tr></thead> <tbody></tbody> </table>"; userTablePlaceholder.InnerHtml = emptyTableHtml; } }
优势:每次仅加载当前页的10条数据,页面加载快,性能更优,适合大数据量场景。
内容的提问来源于stack exchange,提问作者GaryJustGary
相关产品推荐
相关产品推荐

