You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态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:客户端全量分页(简单但大数据量下性能受限)

修改后台代码,一次性渲染所有记录,前端通过控制行的显示/隐藏实现分页:

  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;
    }
}
  1. 修改后台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:

  1. 后台添加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
    });
}
  1. 修改前端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);
    }
});
  1. 调整后台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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 01:12:32