简介:这份资源面向使用 ASP.NET 4.0 与 SQL Server 2008 开发信息系统的开发者,针对 VS 自带 GridView 在交互体验与可移植性上的不足,提供一套经国外论坛筛选后整理的实用 GridView 实例。它完整实现了数据的增、删、改操作,并以 Ajax 无刷新方式提交,适合需要快速搭建后台数据管理页面的初中级 .NET 开发者参考移植。压缩包共 2 个文件,包含 htm 说明页与 rar 源码包,整体约 1.52MB,体积轻量、结构简单,便于直接解压查看与二次改造。目前已有 651 人学习下载,说明其在同类控件方案中具备一定参考价值。读者可从中获得可运行的增删改示例代码、Ajax 局部刷新思路以及可迁移到自身项目的控件配置方式,适合作为收藏备用的 GridView 实践模板。
1. 一个老控件的新玩法:为什么我还在用 GridView 做后台管理
接手一个内部设备台账系统时,需求方只提了一句话:列表能看、能改、能删,别刷新整页。听起来像在为难人,但 ASP.NET 里那个被很多人嫌弃“老掉牙”的 GridView,配上 SqlDataSource 和 UpdatePanel,恰好能把这件事干得又快又稳。这套组合的核心价值在于:行内编辑、分页排序、增删改查几乎不用手写 SQL 和回发逻辑,服务端控件把脏活全包了。适合谁?适合要在一两天内交付一个可维护后台列表页的开发者,也适合想搞懂 ASP.NET 回发机制与 AJAX 局部刷新如何协作的人。下面按“能干什么、怎么跑、怎么改”的顺序,把这个 GridView 增删改示例拆开讲。
2. 项目定位与技术栈:GridView + SqlDataSource + UpdatePanel 的三件套
2.1 这个示例到底解决了什么问题
后台管理页面里最高频的交互就是“表格里改一行数据”。传统做法是点编辑跳转到另一个页面,改完再跳回来,用户来回折腾,开发者还要写一堆参数传递和状态保持代码。这个示例的思路是把编辑态直接嵌在行内:点“编辑”按钮,当前行变成输入框;点“更新”,只把这一行的字段提交回服务器;点“删除”,弹个确认框再删。整个过程页面不整体刷新,靠的是 UpdatePanel 把回发包成异步请求。
从功能面看,它覆盖了列表展示、分页、排序、行内编辑、新增、删除六件事。从技术面看,它把 ASP.NET WebForms 的几个关键机制串在了一起:GridView 的模板列与命令事件、SqlDataSource 的参数化增删改、UpdatePanel 的局部渲染。这三者各管一段,职责清晰,改起来不容易互相踩脚。
需要说明的是,这个示例面向的是 SQL Server 数据库,连接串写在 web.config 里,表结构简单,通常就是一张带主键的业务表。它不涉及多层架构、不涉及 ORM,就是一个“够用就好”的后台列表页原型。对于内部工具、管理后台、数据维护页这类场景,这种朴素结构反而比过度设计更容易交接。
2.2 技术栈逐项拆解与选型理由
| 层次 | 技术 | 在本项目中的角色 |
|---|---|---|
| 页面框架 | ASP.NET WebForms | 承载 .aspx 页面与后台代码 |
| 数据绑定 | GridView | 渲染表格、分页排序、行内编辑 |
| 数据访问 | SqlDataSource | 声明式配置 Select/Insert/Update/Delete |
| 局部刷新 | UpdatePanel + ScriptManager | 把回发包成异步,避免整页闪烁 |
| 数据库 | SQL Server | 存储业务表数据 |
| 配置 | web.config | 存放连接字符串 |
为什么用 SqlDataSource 而不是手写 ADO.NET?因为这个示例的重点是“快速可用的增删改”,SqlDataSource 的声明式配置能把 SQL 和参数直接写在标记里,GridView 的编辑命令可以自动对接 UpdateCommand,省掉大量胶水代码。代价是 SQL 与页面耦合较紧,复杂查询或事务场景就不合适了,这一点后面改造章节会讲怎么拆。
为什么用 UpdatePanel 而不是纯前端 AJAX?WebForms 的回发模型和 UpdatePanel 是原生搭配,不需要额外写 JavaScript 去拼请求、解析响应。对于不熟悉前端框架的维护者,这套东西的可读性更高。当然它传输的是部分页面 HTML 而非纯数据,带宽上不如接口式 AJAX 经济,这是它的固有取舍。
2.3 目录结构与文件职责
一个典型的 WebForms 项目里,与本示例相关的文件大致如下:
GridViewDemo/ ├── App_Data/ # 数据库文件或备份(若用附加文件方式) ├── Default.aspx # 列表页:GridView + SqlDataSource + UpdatePanel ├── Default.aspx.cs # 后台代码:少量事件处理与提示逻辑 ├── Web.config # 连接字符串、编译与运行时配置 └── Site.master # 母版页(可选):统一页头与 ScriptManagerDefault.aspx是主战场,GridView 的列定义、命令按钮、SqlDataSource 的四条命令都写在这里。Default.aspx.cs通常只处理 RowUpdated、RowDeleted 这类事件,用来给用户弹提示或做二次校验。Web.config里的连接串是跑起来的第一道坎,后面会专门讲。母版页不是必须的,但如果项目里有多张列表页,把 ScriptManager 放在母版页里能避免每页重复声明。
3. 跑起来:从建表到页面局部刷新的完整步骤
3.1 建表与连接字符串配置
先准备一张最小的业务表。以设备台账为例,主键用自增整型,字段保持简单,方便对照后面的参数配置:
CREATE TABLE DeviceLedger ( DeviceId INT IDENTITY(1,1) PRIMARY KEY, DeviceName NVARCHAR(50) NOT NULL, Owner NVARCHAR(30) NULL, Status NVARCHAR(20) NULL, UpdatedAt DATETIME NULL );主键DeviceId是后面 Update 和 Delete 命令的定位依据,必须有。UpdatedAt用来演示“更新时自动写时间”这类小需求,可以在 UpdateCommand 里用GETDATE()处理。
连接字符串写进Web.config的connectionStrings节点:
<configuration> <connectionStrings> <add name="LedgerConn" connectionString="Data Source=.;Initial Catalog=GridDemo;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>Data Source=.表示本机默认实例,Integrated Security=True表示用 Windows 身份验证。如果换成账号密码方式,改成User ID=...;Password=...即可。这里最容易翻车的是数据库实例名和身份验证模式对不上,现象是页面一打开就报“无法打开登录所请求的数据库”,排查时先确认 SSMS 里能用同样的凭据连上。
3.2 GridView 与 SqlDataSource 的声明式绑定
页面标记的核心是把 GridView 的列和 SqlDataSource 的命令对齐。下面这段是精简后的关键部分:
<asp:ScriptManager ID="ScriptManager1" runat="server" /> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1" AutoGenerateColumns="False" DataKeyNames="DeviceId" AutoGenerateEditButton="True" AutoGenerateDeleteButton="True" AllowPaging="True" PageSize="10" OnRowUpdated="GridView1_RowUpdated" OnRowDeleted="GridView1_RowDeleted"> <Columns> <asp:BoundField DataField="DeviceName" HeaderText="设备名称" /> <asp:BoundField DataField="Owner" HeaderText="责任人" /> <asp:BoundField DataField="Status" HeaderText="状态" /> <asp:BoundField DataField="UpdatedAt" HeaderText="更新时间" ReadOnly="True" /> </Columns> </asp:GridView> </ContentTemplate> </asp:UpdatePanel>DataKeyNames="DeviceId"是关键参数,它告诉 GridView 用哪一列作为行的唯一标识,编辑和删除时这个值会随命令一起回传。AutoGenerateEditButton和AutoGenerateDeleteButton打开后,GridView 会自动生成“编辑/更新/取消”和“删除”按钮,省去手写模板列。ReadOnly="True"让更新时间列在编辑态下不可改,避免用户误填。
UpdatePanel包住 GridView,配合页面顶部的ScriptManager,所有由 GridView 触发的回发都会走异步通道。注意ScriptManager必须存在且位于 UpdatePanel 之前,否则运行时会直接报错。
3.3 四条命令的配置与参数映射
SqlDataSource 的增删改查命令写在标记里,参数用控件值或主键自动映射:
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:LedgerConn %>" SelectCommand="SELECT DeviceId, DeviceName, Owner, Status, UpdatedAt FROM DeviceLedger ORDER BY DeviceId" UpdateCommand="UPDATE DeviceLedger SET DeviceName=@DeviceName, Owner=@Owner, Status=@Status, UpdatedAt=GETDATE() WHERE DeviceId=@DeviceId" DeleteCommand="DELETE FROM DeviceLedger WHERE DeviceId=@DeviceId" InsertCommand="INSERT INTO DeviceLedger (DeviceName, Owner, Status, UpdatedAt) VALUES (@DeviceName, @Owner, @Status, GETDATE())"> <UpdateParameters> <asp:Parameter Name="DeviceName" Type="String" /> <asp:Parameter Name="Owner" Type="String" /> <asp:Parameter Name="Status" Type="String" /> <asp:Parameter Name="DeviceId" Type="Int32" /> </UpdateParameters> <DeleteParameters> <asp:Parameter Name="DeviceId" Type="Int32" /> </DeleteParameters> </asp:SqlDataSource>参数名必须与 SQL 里的占位符一一对应,DeviceId的类型写成Int32与表结构一致。Update 命令里UpdatedAt=GETDATE()是服务端时间,不依赖客户端时钟,这点比让用户填时间可靠。Delete 只按主键删,参数少但足够安全。
这里有个容易忽略的细节:UpdateParameters里没有显式指定UpdatedAt,因为它在 SQL 里由GETDATE()生成,不需要从页面传值。如果误加一个同名参数却没在 SQL 里用,运行时会报“参数未使用”之类的错误,排查时对照 SQL 占位符逐个核对即可。
3.4 后台事件与操作反馈
后台代码通常只做两件事:更新/删除成功后给提示,失败时记录原因。示例:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e) { if (e.Exception == null) { lblMsg.Text = "更新成功"; } else { lblMsg.Text = "更新失败:" + e.Exception.Message; e.ExceptionHandled = true; // 阻止异常继续向上抛 } } protected void GridView1_RowDeleted(object sender, GridViewDeletedEventArgs e) { lblMsg.Text = e.Exception == null ? "删除成功" : "删除失败"; if (e.Exception != null) e.ExceptionHandled = true; }e.ExceptionHandled = true这行很重要,它把异常标记为已处理,页面不会跳到黄页错误,用户能看到友好提示。lblMsg是页面上一个 Label,放在 UpdatePanel 内部才能随局部刷新一起更新。如果放在面板外,提示文字要等整页刷新才出现,体验上会显得“点了没反应”。
4. 核心代码拆解:一次行内编辑从点击到落库的链路
4.1 回发链路:按钮点击后发生了什么
点“编辑”时,GridView 把当前行切换为编辑态,这一步是纯服务端状态变化,通过 UpdatePanel 异步回发完成。点“更新”时,GridView 收集该行各输入控件的值,触发RowUpdating,随后 SqlDataSource 用UpdateCommand执行更新,成功后触发RowUpdated。整条链路上,DataKeyNames提供主键,UpdateParameters提供字段值,两者缺一不可。
理解这条链路的价值在于排查问题时有据可依:如果更新后数据没变,先看 SQL 的 WHERE 条件是否命中;如果报参数缺失,先看UpdateParameters与占位符是否对齐;如果页面整页刷新,先看 GridView 是否真的在 UpdatePanel 内。把链路拆成“按钮 → 事件 → 命令 → 参数 → 数据库”五段,逐段验证,比盲目改代码高效得多。
4.2 参数化与主键定位的配合
行内编辑最容易出的问题是“更新到了错误的行”。根源通常是主键没有正确参与 WHERE 条件。GridView 通过DataKeyNames把主键存进行状态,SqlDataSource 在执行 Update 时自动把该值绑定到@DeviceId。前提是UpdateParameters里声明了同名同类型的参数,且 SQL 的 WHERE 用了它。
如果表的主键是复合键,DataKeyNames可以写多个字段,用逗号分隔,对应的 WHERE 条件也要写全。复合键场景下漏写一个条件,就会出现“更新影响多行”的隐患。这个示例用单主键是为了降低理解成本,实际项目里遇到复合键,务必在 WHERE 里把每个键字段都带上。
4.3 局部刷新的边界:UpdatePanel 里放什么
UpdatePanel 的放置位置直接决定刷新范围。把 GridView 和提示 Label 都放进去,更新后表格和提示一起变;如果把 Label 放在外面,提示就要等下一次整页加载。常见做法是把“表格 + 操作提示 + 分页”整体包进一个面板,页头、导航、页脚留在外面,这样既保证交互区域一致刷新,又不会把无关内容重复传输。
还要注意 UpdatePanel 的UpdateMode属性。默认是Always,面板内任何异步回发都会刷新整个面板;改成Conditional后,只有显式调用Update()或触发注册的控件才刷新。对于表格页,Always通常够用,但如果面板里塞了很多静态内容,Conditional能减少不必要的 HTML 传输。
5. 避坑与排查:这套组合最容易翻车的五个地方
5.1 现象:点更新后数据没变,也没报错
原因多半是 WHERE 条件没命中,或者DataKeyNames写错列名。解决:在 SSMS 里把生成的 UPDATE 语句手工跑一遍,确认影响行数;再检查DataKeyNames是否与主键列名完全一致,大小写不敏感但拼写必须对。
5.2 现象:页面报“未能加载视图状态”或“无效的回发”
原因通常是 ViewState 被禁用,或者页面在回发前被整体替换。解决:确认EnableViewState没有被误关;如果用了母版页,检查 ScriptManager 是否只声明了一次,重复声明会导致脚本冲突。
5.3 现象:删除按钮点了没反应,或删错行
原因可能是AutoGenerateDeleteButton打开了但DataKeyNames缺失,导致主键没随命令回传。解决:补上DataKeyNames,并在DeleteParameters里声明对应参数;同时给删除加OnClientClick="return confirm('确认删除?');",避免误触。
5.4 现象:分页后编辑,更新到了第一页的数据
原因是对分页状态与主键的对应关系理解有误,或手工拼接了错误的索引。解决:坚持用DataKeyNames定位,不要用行号做 WHERE 条件;分页参数交给 GridView 自己管理,不要手动改PageIndex后忘记重新绑定。
5.5 现象:连接字符串正确,但部署到服务器后连不上
原因是开发机用 Windows 身份验证,服务器上的应用池账户没有数据库权限。解决:给应用池身份单独授权,或改用 SQL 账号并在连接串里写明;排查时先看服务器事件日志里的登录失败详情,比在页面上猜快得多。
6. 二次开发:把声明式配置拆成可维护的分层结构
这套示例跑通之后,下一步往往是“加字段、加校验、加权限”。如果继续把所有逻辑堆在 aspx 里,改到第三张表就会开始难受。我的做法是保留 GridView 的展示能力,但把数据访问从 SqlDataSource 里抽出来,换成后台代码调用参数化命令,页面只负责绑定。这样 SQL 集中在一处,加字段时改一个方法即可,也方便加事务和日志。
具体改造分三步。第一步,把SelectCommand挪到后台,用一个返回DataTable的方法替代,GridView 改为在Page_Load里手动绑定。第二步,把 Update 和 Delete 改成显式方法,参数从 GridView 的Keys和NewValues里取,这样可以在执行前插入校验,比如状态值白名单、责任人非空。第三步,给操作加一层轻量日志,记录谁在什么时候改了哪一行,出问题时能追溯。
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e) { int id = Convert.ToInt32(GridView1.DataKeys[e.RowIndex].Value); string name = e.NewValues["DeviceName"]?.ToString(); if (string.IsNullOrWhiteSpace(name)) { e.Cancel = true; // 校验不通过,取消本次更新 lblMsg.Text = "设备名称不能为空"; return; } LedgerRepository.Update(id, name, e.NewValues["Owner"]?.ToString(), e.NewValues["Status"]?.ToString()); }e.Cancel = true是行内校验的开关,置真后本次更新不会下发到数据库,页面停留在编辑态让用户改。e.NewValues按列名取值,比从控件树里找输入框稳妥。把仓储方法独立出来后,单元测试也能覆盖到参数拼装逻辑,不必启动整个页面。
验证改造是否成功,我一般看三个点:编辑一行后刷新页面,数据确实变了;故意留空名称提交,提示出现且数据未变;连续快速点两次更新,不出现重复写入。这三点过了,基本可以放心往生产环境放。从那以后我每次接这类后台列表页,都会先把主键定位和参数映射这两件事在纸上画一遍再动手,省下的返工时间远比画图多。希望帮到你。
本文还有配套的精品资源,点击获取