news 2026/11/4 22:08:00

ASP.NET GridView 与 AJAX 无刷新增删改查的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASP.NET GridView 与 AJAX 无刷新增删改查的完整实战指南

简介:在ASP.NET 4.0与SQL Server 2008环境下开发信息系统时,很多开发者会抱怨自带的GridView控件功能有限、交互体验欠佳。这份从国外论坛精选而来的GridView实用实例,恰好能弥补这一短板:它用非常精简的代码实现了表格数据的增删改查,并借助Ajax技术做到无刷新操作,特别适合需要快速搭建后台管理界面的初中级开发者,也方便有经验者直接改造复用。资源以RAR压缩包形式提供,总共包含两个文件,一个是用于效果预览和操作说明的HTML页面,另一个则是完整可运行的工程源码;整体压缩包大小仅1.52MB,轻巧易携带。目前已有六百五十人学习下载,用户评分与反馈都较为理想。下载后可直接借鉴其分页、编辑、删除等关键操作的实现写法,深入理解Ajax无刷新的交互思路,进而将这套逻辑嵌入自己的项目,有效减少页面回发、提升界面流畅度;同时代码结构清晰、可移植性强,只需调整数据库连接字符串即可接入既有系统,大大省去在海外论坛反复搜索和调试的时间,是一份值得收藏的实用参考。

1. GridView 配 ASP.NET AJAX:后台管理系统里最顺手的表格方案

只要维护过几个后台管理系统,你大概率见过这种场面:打开一个页面,表格带分页、能排序、点删除弹个确认框、编辑时行内变输入框,而且全程不刷新页面。这套交互在 ASP.NET WebForms 时代基本是 GridView 的专场,配合 AJAX 无刷新能力,能把增删改查的重复劳动压到最低。它的价值不在于技术多新,而在于落地快、模型统一:从 SqlDataSource 拖拽绑定到模板列里塞按钮,半天就能交付一个能用的数据维护列表。而今天要拆的这套资源,正好把 GridView 加 AJAX 效果的完整路径和里头的坑一次讲透。

2. 选择 GridView 的工程理由与准备:先理解它的运行模型

2.1 服务器控件的事件回发模型:AJAX 效果的用户体验起点

GridView 是服务器控件,这意味着它的所有交互默认都是回发机制:点排序、点翻页、点编辑,都会把整个页面 POST 到服务端,处理完再把完整 HTML 返回刷新。用户体验上是闪屏加滚动条回顶,非常生硬。要做出 AJAX 效果,必须先理解它的生命周期事件顺序:每次回发都会经历 Load、RowCommand、RowEditing、RowUpdating、RowDeleting 这一串事件,再进入 DataBind 重绘表格。

所以做 AJAX 方案时,重点在于控制回发范围和减少全页刷新,而不是避开回发。UpdatePanel 就是宰割这个场景的工具:把 GridView 丢进 UpdatePanel,配合 ScriptManager,回发就变成异步局部更新。对用户来说,页面没闪,滚动条也不会跳。

2.2 数据绑定路径:从 SqlDataSource 到手动绑定

GridView 最省事的数据来源是 SqlDataSource 控件,设计器里配上连接字符串和 SelectCommand 就能跑。但这类声明式绑定的缺点是 SQL 和页面标记混在一起,并且增删改要分别写 InsertCommand、UpdateCommand、DeleteCommand,参数多了以后维护起来比较头疼。

我更习惯在代码里手动绑定,逻辑集中,也方便加权限判断。常见做法是先写好取数方法:

private DataTable GetUserList(string keyword) { string connStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; string sql = @"SELECT u.UserId, u.UserName, u.Email, u.CreatedAt, r.RoleName FROM dbo.Users u INNER JOIN dbo.UserRoles r ON u.RoleId = r.RoleId WHERE (@Keyword = '' OR u.UserName LIKE '%' + @Keyword + '%') ORDER BY u.CreatedAt DESC"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@Keyword", keyword ?? ""); SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; } }

这段代码做了三件事:拼接带参数化查询的 SQL,通过适配器填充 DataTable,最后返回绑定数据源。参数化查询这一步不能省,直接拼接用户输入会留下注入风险;AddWithValue 在 SQL Server 下虽然方便,但要注意参数类型和长度最好明确指定,否则索引可能走不准,数据量一大就慢。绑定动作放在 Page_Load 里,并且用 IsPostBack 判断避免每次回发都重复查库:

protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { BindGrid(); } } private void BindGrid() { DataTable dt = GetUserList(txtKeyword.Text.Trim()); GridView1.DataSource = dt; GridView1.DataBind(); }

这里的经验是:IsPostBack 判断是 WebForms 的第一条铁律。忘记写,查询就会在每次回发里重复执行;写多了,会导致你手动恢复排序和分页状态时多一层干扰。GridView 的 DataSource 只存在于当前页面生命周期里,不持久化,所以每次回发后只要重新绑定,表格会自动恢复成第一次绑定的样子,除非你把状态写在 ViewState 或 Session 里。

2.3 模板列的布局思路:把表格改造成操作台

GridView 的 BoundField 只适合展示纯文本,真要做出增删改的交互效果,模板列才是核心。模板列里可以放 Label、TextBox、Button 甚至 DropDownList,配合 EditItemTemplate 区分展示态和编辑态。典型的操作列结构如下:

<asp:TemplateField HeaderText="操作"> <ItemTemplate> <asp:LinkButton ID="btnEdit" runat="server" CommandName="Edit" CommandArgument='<%# Eval("UserId") %>' Text="编辑" /> <asp:LinkButton ID="btnDelete" runat="server" CommandName="Delete" CommandArgument='<%# Eval("UserId") %>' Text="删除" OnClientClick="return confirm('确认删除该记录?');" /> </ItemTemplate> <EditItemTemplate> <asp:LinkButton ID="btnSave" runat="server" CommandName="Update" Text="保存" /> <asp:LinkButton ID="btnCancel" runat="server" CommandName="Cancel" Text="取消" /> </EditItemTemplate> </asp:TemplateField>

CommandName 是 GridView 内置命令的开关:Edit 触发 RowEditing,Update 触发 RowUpdating,Delete 触发 RowDeleting,Cancel 触发 RowCancelingEdit。CommandArgument 用来携带主键值,因为模板列里不方便直接用 DataKeys,手动传参最稳。之所以把主键通过 CommandArgument 传出来,是因为 RowCommand 事件在 RowEditing 之前触发,有些逻辑(比如权限校验)放在这里最合适,而且这个参数在更新和删除时都能拿到,不依赖 DataKeyNames 是否设置。

2.4 空数据与加载状态的收尾工作

表格绑定了数据源以后,直接跑会发现问题:没有数据时页面空白一坨,用户不知道是查询条件太严还是系统压根没数据。GridView 提供了 EmptyDataTemplate,当绑定数据源为空时展示这段模板。

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="UserId" OnRowCommand="GridView1_RowCommand" OnRowEditing="GridView1_RowEditing" OnRowUpdating="GridView1_RowUpdating" OnRowDeleting="GridView1_RowDeleting" OnRowCancelingEdit="GridView1_RowCancelingEdit"> <EmptyDataTemplate> <div class="empty-tip">当前条件下没有找到任何数据,试试清空筛选条件。</div> </EmptyDataTemplate> </asp:GridView>

模板里可以放任意 HTML,甚至按钮。一个合格的后台列表要有空数据提示,不然用户在空页面里很容易怀疑是系统出了故障。这个细节在验收时经常被列为体验问题,提前处理掉省得返工。

3. 无刷新增删改的一次到位实现:保底方案与 Ajax 增强

3.1 轻量级平滑方案:ScriptManager 加 UpdatePanel

整套 GridView 的 AJAX 增强基础是 ScriptManager 和 UpdatePanel。UpdatePanel 会把内部控件触发的回发截住,转成异步 POST,服务端处理完只返回需要更新的局部 HTML 片段。这种做法的优点是改动量小,原来的事件代码一行不用改,缺点是会引入 ViewState 和 ScriptManager 的额外开销,页面多了以后脚本体积会变大。

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true"> </asp:ScriptManager> <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="UserId" AllowPaging="True" PageSize="10" OnPageIndexChanging="GridView1_PageIndexChanging"> </asp:GridView> </ContentTemplate> </asp:UpdatePanel>

代码里 UpdateMode 设为 Conditional,意思是只有内部控件触发事件时才更新,外部控件不会引起它刷新,这是减少无效回发的关键参数。EnablePartialRendering 开启后回发响应里只带需要更新的部分,而不是整个 HTML。要提醒的是,UpdatePanel 里的 ScriptManager 一个页面只能有一份,而且必须在 UpdatePanel 之前注册。

3.2 更干净的方案:jQuery 调 WebMethod 做行内编辑

UpdatePanel 虽然省事,但它依然走的是完整页面生命周期,服务端开销并没有减少。另一种做法是页面放 WebMethod,用 jQuery 的 ajax 方法直接调,全程不触发回发。适合做那种点击编辑后行内出现保存框、再点保存异步提交的场景。

先在后端代码声名 WebMethod:

[System.Web.Services.WebMethod] public static string UpdateUser(string userId, string userName, string email) { string connStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; string sql = @"UPDATE dbo.Users SET UserName = @UserName, Email = @Email WHERE UserId = @UserId"; using (SqlConnection conn = new SqlConnection(connStr)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@UserId", userId); cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@Email", email); conn.Open(); int affected = cmd.ExecuteNonQuery(); return affected > 0 ? "ok" : "fail"; } }

WebMethod 必须是 static 方法,这是 ASP.NET AJAX 调用服务端方法的基本约束。它不经过页面生命周期,所以不能用 ViewState、不能用 this 访问控件。返回结果用简单字符串即可,复杂数据用 JSON。页面端通过 ASP.NET AJAX 的 PageMethods 机制来调用:

function saveUser(userId) { var row = $('#row-' + userId); var userName = row.find('.txt-name').val(); var email = row.find('.txt-email').val(); PageMethods.UpdateUser(userId, userName, email, onSaveSuccess, onSaveFail); } function onSaveSuccess(result) { if (result === "ok") { location.reload(); } else { alert("保存失败,请检查输入内容"); } } function onSaveFail(error) { console.log(error); alert("请求异常,稍后重试"); }

要跑通 PageMethods,页面上得先加一句EnablePageMethods="true":

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true"> </asp:ScriptManager>

这套方案的好处是请求体积小,只传 JSON,服务端也只跑一个方法,不像 UpdatePanel 要把整个表格状态来回传。缺点是编辑状态的管理要自己写前端逻辑,没有服务器控件那种自动切换模板的能力,初学者的维护成本高一些。

3.3 事件分工表:RowCommand、RowEditing、RowUpdating 各管一段

GridView 的事件并不是所有按钮都走同一套逻辑,分清职责可以避免一半以上的坑。按我实际项目里的习惯,分工是这样的:RowCommand 处理自定义按钮和删除前的逻辑;RowEditing 负责把行切换到编辑态;RowUpdating 执行真正 UPDATE;RowDeleting 执行 DELETE;RowCancelingEdit 取消编辑。

protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "Edit") { // 可以做权限校验或记录日志,但不要在这里改行状态 int userId = Convert.ToInt32(e.CommandArgument); ViewState["EditingUserId"] = userId; } else if (e.CommandName == "Delete") { DeleteUser(Convert.ToInt32(e.CommandArgument)); BindGrid(); } } protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e) { int userId = Convert.ToInt32(GridView1.DataKeys[e.RowIndex].Value); string userName = ((TextBox)GridView1.Rows[e.RowIndex].FindControl("txtName")).Text.Trim(); string email = ((TextBox)GridView1.Rows[e.RowIndex].FindControl("txtEmail")).Text.Trim(); UpdateUser(userId, userName, email); GridView1.EditIndex = -1; BindGrid(); } protected void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e) { int userId = Convert.ToInt32(GridView1.DataKeys[e.RowIndex].Value); DeleteUser(userId); BindGrid(); }

参数说明:RowCommand 里不能用 e.RowIndex 取索引,因为它是命令事件不是数据事件,CommandArgument 才是可靠的传参通道。RowUpdating 和 RowDeleting 里取主键用 DataKeys[e.RowIndex],前提是 GridView 设置了 DataKeyNames。FindControl 查的是编辑态模板里的控件,名称必须和 aspx 里一致。每次操作完,Update 或 Delete 后把 EditIndex 重置为 -1,再重新绑定,这是让表格回到展示态的关键步骤,漏掉的话会出现行一直处于编辑状态的诡异现象。

4. GridView 与 AJAX 组合的避坑记录:数据、事件和视图状态

4.1 编辑保存后行内出现的时间变成 "0001/1/1"

现象:日期列用的是模板列里绑Eval("CreatedAt"),编辑保存后显示异常。

原因:Binding 时没有统一格式化,日期类型进入 BoundField 或 TextBox 后回传格式被文化设置干扰,SQL Server 里的 datetime 被读成了默认值。

解决:模板列里改写绑定表达式,加格式化:

<asp:Label ID="lblCreatedAt" runat="server" Text='<%# Eval("CreatedAt", "{0:yyyy-MM-dd HH:mm}") %>' />

同时在 Update 参数里对日期做 TryParse 处理,解析失败时给默认值DateTime.Now。日期格式化要放在展示层,不要依赖 SQL 的隐式转换。

4.2 更新表格后页面报 "DataKeyNames 未设置"

现象:点击行内保存按钮,后台抛 ArgumentException。

原因:RowUpdating 里通过DataKeys[e.RowIndex]取主键,但 GridView 没配置 DataKeyNames。

解决:aspx 属性里补上主键字段,多个字段用逗号分隔,例如DataKeyNames="UserId,RoleId"。这里有个更隐蔽的坑:如果查询结果里没有这个字段名,同样会报错,所以 SELECT 语句里必须显式返回主键列。

4.3 UpdatePanel 内 GridView 点击编辑却整页刷新

现象:按钮点击后浏览器地址栏带上了参数,页面闪烁。

原因:UpdatePanel 的触发器没有注册对应按钮的 Click 事件。LinkButton 默认启用回发,必须让面板知道这个按钮的回发由它接管。

解决:在 UpdatePanel 的 Triggers 里显式添加 AsyncPostBackTrigger:

<Triggers> <asp:AsyncPostBackTrigger ControlID="btnSearch" EventName="Click" /> </Triggers>

或者更省事的方式是把触发按钮直接放进 UpdatePanel 的 ContentTemplate 内部,面板会自动接管。但注意:放进内部会导致整个面板被刷新,如果内部还有其它展示信息,交互就会比预期更重。

4.4 删除后页码状态乱跳,回到第一页

现象:在第 5 页删除一条数据,页面自动跳到第 1 页。

原因:重新绑定时没有恢复当前页索引,GridView 的 PageIndex 被重置成 0。

解决:删除和更新操作里记录当前页,重新绑定前恢复:

int currentPage = GridView1.PageIndex; DeleteUser(userId); BindGrid(); GridView1.PageIndex = currentPage;

这时要注意另一种边界:当前页最后一条记录删掉后,页面实际数据行数已经不足一页,再把 PageIndex 设回原来的值会越界。稳妥的做法是判断当前页是否超出总页数:

if (GridView1.PageIndex > GridView1.PageCount - 1) { GridView1.PageIndex = GridView1.PageCount - 1; }

4.5 ViewState 过大导致页面提交特别慢

现象:GridView 数据量几百行时,页面响应急剧变慢,网络面板里 __VIEWSTATE 字段有几 KB 甚至几十 KB。

原因:GridView 默认把每行数据都序列化进 ViewState,用于回发时对照状态。行数越多,隐藏字段越大。

解决:在 GridView 上关闭不需要的视图状态,EnableViewState="false",前提是每次回发都重新绑定数据。配合ViewStateMode="Disabled"控制页面级开关也可以。但如果你的更新逻辑里要读旧行的值,关闭 ViewState 后取不到,所以只适合纯展示场景。增删改页面我会控制行数,PageSize 不超过 20,ViewState 再大也有限。

4.6 排序箭头点了没反应,表格排序失效

现象:点击列标题,表格没有变化,或者报"无法找到排序属性"。

原因:GridView 的排序机制依赖 AllowSorting 属性,并且每个可排序列都要设置 SortExpression。只开 AllowSorting 但列没配 SortExpression 是不生效的。

解决:给列加上 SortExpression,并实现 Sorting 事件:

<asp:BoundField DataField="CreatedAt" HeaderText="创建时间" SortExpression="CreatedAt" />
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e) { ViewState["SortExpression"] = e.SortExpression; DataTable dt = GetUserList(txtKeyword.Text.Trim()); DataView dv = dt.DefaultView; dv.Sort = e.SortExpression + (ViewState["SortDirection"] == "DESC" ? " DESC" : " ASC"); if (ViewState["SortDirection"] == "ASC") ViewState["SortDirection"] = "DESC"; else ViewState["SortDirection"] = "ASC"; GridView1.DataSource = dv.ToTable(); GridView1.DataBind(); }

DataView 的 Sort 属性接收排序表达式,这里用 ViewState 保存方向和表达式,实现点击列头在 ASC 与 DESC 之间切换。细节是:数据源如果是 DataTable,必须在排序前把字符串拼接好,不要直接在 SQL 里拼排序字段,会有注入风险。

5. 分页与大数据量:GridView 在数据膨胀后的三种解法

5.1 AllowPaging 与页索引事件的基础用法

GridView 自带分页,开 AllowPaging 就能出现页码条。默认是 10 条一页,PageSize 控制行数。分页触发的不是 RowCommand,而是 PageIndexChanging 事件。这里有个常识必须反复强调:它只是 UI 层的分页,数据源每次还是全量查询,只是按页索引展示一小段。数据量几百行无所谓,几万行就不合适了。

protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e) { GridView1.PageIndex = e.NewPageIndex; BindGrid(); }

这个写法的基础原理是重新把数据绑定一遍,并且把 GridView 的当前页索引覆盖成新值。注意不能用e.NewPageIndex直接赋值给 GridView 的 PageIndex 属性后就不管了,必须重新 DataBind,否则页面上显示的还是旧数据。

5.2 千万级数据下的转向:前端分页与存储过程分页

表格数据超过 5 万行时,GridView 的假分页就会明显拖慢响应。此时正确的方案是存储过程分页,每次只查当前页的数据。SQL Server 2012 以上版本可以用 OFFSET/FETCH:

CREATE PROCEDURE [dbo].[GetUsersPaged] @PageIndex INT = 1, @PageSize INT = 10, @Keyword NVARCHAR(50) = '' AS BEGIN SET NOCOUNT ON; DECLARE @Offset INT = (@PageIndex - 1) * @PageSize; SELECT u.UserId, u.UserName, u.Email, u.CreatedAt FROM dbo.Users u WHERE (@Keyword = '' OR u.UserName LIKE '%' + @Keyword + '%') ORDER BY u.CreatedAt DESC OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY; SELECT COUNT(1) AS TotalCount FROM dbo.Users u WHERE (@Keyword = '' OR u.UserName LIKE '%' + @Keyword + '%'); END

前端代码里绑定用存储过程,并保存总页数来计算页码条。这时的 GridView 分页逻辑就变成了:PageIndexChanging 里改 PageIndex 后带着新页索引重新执行存储过程。总记录数用第二张表的返回值,GridView 的页数不再由内部状态决定。OFFSET 分页有个边界条件是 ORDER BY 必须有唯一性,否则不稳定,排序键重复会导致翻页有重行。

5.3 ViewState 与分页状态的叠加坑

GridView 默认存储当前页码和排序方向,这些状态放在哪一层决定你关闭 ViewState 后是否还能翻页。如果你设置EnableViewState="false",分页索引不会自动保存,PageIndexChanging 重置就会回到第一页。

常见做法是明确把分页状态放在 ViewState 里:

protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e) { ViewState["PageIndex"] = e.NewPageIndex; GridView1.PageIndex = e.NewPageIndex; BindGrid(); } private void BindGrid() { DataTable dt = GetUserList(txtKeyword.Text.Trim(), GridView1.PageIndex, GridView1.PageSize); GridView1.DataSource = dt; GridView1.DataBind(); }

页索引从 ViewState 读,绑定数据时再作为参数传入存储过程。这套逻辑在关闭 GridView 的 ViewState 后依然成立,因为状态被显式托管了。我一般不会完全关闭 ViewState,但如果页面里 GridView 之外还有大块输入内容,关掉它能明显降低回发体积。

5.4 使用 DataReader 与 DataTable 的取舍

分页方案选定后,取数方式也影响性能。SqlDataAdapter 填充 DataTable 对内存不友好,大数据量场景可以考虑 SqlDataReader 循环读取。但 GridView 的 DataSource 支持 IDataSource 接口,直接绑定 DataReader 没有问题。这里给出一个折中方案:数据量小用 DataTable,数据量大用存储过程分页输出小表,避免一次性把海量记录装进内存里。DataReader 是只读前向的,不能排序,所以它只适合不再做二次加工的场景,排序需求得放在 SQL 里解决。

6. 进阶一步:让操作列支持批量勾选与二次确认

操作列从单行编辑升级到批量处理,是后台开发里很常见的高级需求。思路是在模板列第一列加 CheckBox,全选按钮通过 jQuery 控制所有行的选中状态,选中的主键通过隐藏字段收集,提交时拼成一个字符数组传给服务端。服务端方法用数组参数接收,循环执行 UPDATE 或者 DELETE,也可以拼成 IN 条件一次执行。

前端勾选逻辑:

$(function () { $("#chkAll").change(function () { var isChecked = $(this).prop("checked"); $(".row-checkbox").prop("checked", isChecked); }); }); function getSelectedIds() { var ids = []; $(".row-checkbox:checked").each(function () { ids.push($(this).val()); }); return ids; } function batchDelete() { var ids = getSelectedIds(); if (ids.length === 0) { alert("请先勾选要删除的记录"); return; } if (!confirm("确认删除选中的 " + ids.length + " 条记录?")) { return; } PageMethods.BatchDelete(ids.join(","), onBatchSuccess, onBatchFail); }

服务端批量删除:

[System.Web.Services.WebMethod] public static string BatchDelete(string idList) { string[] ids = idList.Split(','); string connStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; using (SqlConnection conn = new SqlConnection(connStr)) { string sql = @"DELETE FROM dbo.Users WHERE UserId IN (SELECT value FROM STRING_SPLIT(@IdList, ','))"; using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@IdList", idList); conn.Open(); int affected = cmd.ExecuteNonQuery(); return "ok:" + affected; } } }

这里用 STRING_SPLIT 把逗号分隔的字符串拆成行集,避免循环执行 SQL,一次命令完成批量删除。前端通过 PageMethods 把 idList 传进来,返回字符串里带受影响行数,弹窗里可以直观看到删了多少条。如果用的是旧版 SQL Server 不兼容 STRING_SPLIT,就改用循环参数或者临时表方案,但效果相同。

这套实现补上了一个常见短板:普通 GridView 的删除只能逐条点,批量勾选加异步提交,是后台管理场景里高频出现的交互。再往后如果要优化,可以把批量操作按钮放进工具栏区域,通过 AJAX 无刷新更新 GridView 的 DataSource,但那已经是组件化思路了。

想起当年第一次在项目里用 GridView 做维护页面,只绑了个数据源就交付了,结果用户反馈说点击编辑之后页面刷得头晕,数据一多点删除还会跳回第一页。后来把 UpdatePanel 加上、DataKeyNames 补齐、分页状态做托管,一套下来才算真正能用。从那以后我每次做 GridView 都会强制检查四样东西:DataKeyNames、IsPostBack 绑定、EventName 触发关系、分页状态恢复。这套流程走完,表格页面基本不会翻车。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 20:37:46

AI视频接单全流程:零基础做抖音/B站/小红书爆款视频

1. 项目概述&#xff1a;这不是“AI视频课”&#xff0c;而是一套可立即变现的数字内容生产流水线你点开这个标题&#xff0c;第一反应可能是“又一个割韭菜的AI课”——我完全理解。过去两年我帮不下二十个创作者梳理过他们的AI视频工作流&#xff0c;从某高校实验室的学术可视…

作者头像 李华
网站建设 2026/10/22 8:11:52

Nessus漏洞扫描器实战:从安装部署到报告解读

不管是负责企业安全巡检&#xff0c;还是做渗透测试前期的资产摸底&#xff0c;漏洞扫描都是绕不开的一环。而在这一环里&#xff0c;Nessus 这个名字几乎就是“专业扫描器”的代名词。2026 年的新版本在部署方式、扫描引擎、报告呈现上都做了不少改动&#xff0c;我帮朋友部署…

作者头像 李华
网站建设 2026/10/20 18:24:20

Java Web团队研发风格统一实践:从自动化检查到架构约束

做了三年多的JAVA-Web 项目研发&#xff0c;我一度最怕听到的一句话是&#xff1a;"功能没问题&#xff0c;代码风格有点乱&#xff0c;先这样吧。"后来我发现&#xff0c;风格乱不乱不是洁癖问题&#xff0c;而是会实打实吃掉团队效率。今天想聊聊我在团队研发风格统…

作者头像 李华
网站建设 2026/10/24 1:40:53

流程控制实战指南:从嵌套地狱到状态机改造

先聊个真实的场景。某团队接了一个跨平台订单处理系统的维护任务&#xff0c;刚开工就遇到了麻烦&#xff1a;一段下单核心逻辑有七层 if 嵌套&#xff0c;中间还夹着两个 for 循环&#xff0c;循环里又套着 switch&#xff0c;最里层还有一处 return 直接甩锅给调用方。同事打…

作者头像 李华
网站建设 2026/10/24 4:02:29

Linux文件系统实战解析:从目录树到挂载与权限管理

Linux文件系统这堂课&#xff0c;几乎每个学计科的人在第一次打开终端的时候都会撞上它。我在折腾Linux的这几年里&#xff0c;实际感受到的是&#xff1a;搞懂文件系统不在于背下来那张目录树图&#xff0c;而在于弄明白它为什么这样设计&#xff0c;明白之后&#xff0c;什么…

作者头像 李华
网站建设 2026/10/24 6:08:13

海外服务器带宽选型指南:从计费模式到网络调优

搞“海外服务器”这个话题&#xff0c;其实最容易踩的第一个坑&#xff0c;就是冲着“回国线路”这类的宣传词去选服务器。我见过不少朋友&#xff0c;机房还没定&#xff0c;先被各种营销信息绕晕了&#xff0c;最后买回来的带宽规划一塌糊涂——要么贵得离谱&#xff0c;要么…

作者头像 李华