资讯动态

ASP.NET GridView 结合 Ajax 实现无刷新增删改的实践方案

发布时间:2026/10/18 12:54:28 来源:尧图企业网站定制
简介这是一份面向ASP.NET开发者的GridView实用实例针对VS自带GridView功能弱、操作体验差的痛点给出了带Ajax无刷新效果的增删改解决方案。实例基于ASP.NET 4.0与SQL Server 2008代码结构简明适合初级、中级Web开发人员直接移植到信息管理类项目中。压缩包共2个文件包含1个htm说明文档和1个rar源码包整体仅1.52MB轻量易用。已有651人学习浏览。通过下载可拿到完整的GridView增删改示例代码、Ajax异步交互逻辑以及页面展示效果参考尤其适合需要在不刷新页面的前提下快速完成数据维护功能的场景。htm文件可作为使用说明或效果预览rar内为可导入的项目源码按需解压即可复用。1. 这可不是普通的 GridView一个能直接在页面上增删改的 ASP.NET Ajax 表格做过 WebForm 后台管理的人都有这种感受GridView 自带的编辑模式又丑又卡点一下“编辑”整个页面回发屏幕闪一下白输入框才出来。更难受的是每次保存、删除都要刷新整个页面用户面对一个表格来回刷体验跟十年前没什么差别。这个项目解决的就是这个痛点——它把 ASP.NET GridView 和 Ajax 效果结合到一起表格上的每一行都能直接输入内容点“保存”走异步请求点“删除”弹一个确认整个过程不刷新页面增删改查全部在同一个页面里完成。它的数据存储走 SQL Server查询用参数化 SQL整个源码包从页面模板到后端处理都给了完整实现适合 WebForm 老手拿来当基础组件也适合刚接手旧项目的新手照着学 Ajax 回调的写法。2. 项目结构与技术选型从 WebForm 到 Ajax 回调的完整链路2.1 目录结构与文件职责每个文件在干什么把源码包解压之后你会看到典型的 ASP.NET WebApplication 布局。我用 Visual Studio 2019 打开项目文件解决方案里主要就一个网站项目没有多余的项目依赖。核心文件不多但每个都承担明确职责。GridDemo/ ├── Default.aspx // 页面本体GridView 模板和搜索区都在这里 ├── Default.aspx.cs // 页面后台代码负责第一次加载数据 ├── AjaxHandler.ashx // 通用异步处理入口所有增删改请求都走这里 ├── AjaxHandler.ashx.cs // 异步处理逻辑按 action 分发到不同方法 ├── Web.config // 连接字符串、编译配置、Ajax 脚本库配置 ├── App_Data/ │ └── Data.sql // 建库建表的脚本SQL Server 2008 以上可执行 ├── Scripts/ │ └── grid.js // 前端核心脚本封装了收集行数据、发 Ajax 请求 └── Style/ └── site.css // 表格样式、按钮样式、模态框样式这个结构最关键的一点是所有写操作都走一个统一的 .ashx 处理程序而不是每个操作写一个页面。这样前端只管往同一个地址发 POST 请求后端根据请求里的 action 参数决定执行哪个数据库操作。我在类似项目里也一直用这种“单入口分发”的思路好处是后端逻辑集中调试时只需要在一个文件里加断点不用在多个页面之间跳来跳去。2.2 技术栈与运行环境.NET 4.x SQL Server 2008 起这个项目基于 ASP.NET WebForm 4.5 编写理论上 .NET Framework 4.0 以上都能跑。SQL Server 这边只要支持标准 T-SQL 就行2008 R2、2012、2016、2019 我都试过脚本里只用了 CREATE TABLE、INSERT、UPDATE、DELETE 这些基础语句没有用到新版本特性。Ajax 部分没有引入第三方前端框架就是原生 XMLHttpRequest 加 JavaScript 封装所以不需要额外下载 jQuery 或 Vue 等库离线也能跑。我一般会建议第一次跑的人直接用 Visual Studio 2019 或 2022 的 IIS Express不要一开始就挂到正式 IIS 上。原因是 IIS Express 对 .ashx 的权限要求宽松调试断点也容易命中。如果你身边只有 IIS 6 或老版本 IIS需要注意 .ashx 的处理程序映射是否注册这个我在后面避坑章节会详细说。运行环境清单整理成一张表方便你对照检查组件要求说明开发框架.NET Framework 4.54.0 也可但建议用 4.5 及以上数据库SQL Server 2008 R2 及以上Express 版也能用开发工具Visual Studio 2017 以上社区版即可前端依赖无第三方库纯 JS DOM 操作部署环境IIS 6 / IIS Express需配置 .ashx 映射2.3 数据访问层SqlParameter 参数化防注入项目里没有单独建一个 DAL 类库而是在 AjaxHandler.ashx.cs 里直接写了一个简短的数据库操作封装。这个封装虽然简单但有一个非常值得学习的细节所有 SQL 都通过 SqlParameter 传值没有直接把前端传来的字符串拼进 SQL 里。比如删除操作前端传过来一个主键编号后端是这样写的private void DeleteUser(string id) { string sql DELETE FROM [Users] WHERE Id Id; using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(Id, Convert.ToInt32(id)); cmd.ExecuteNonQuery(); } } }逻辑说明这段代码先定义参数化的删除语句Id是一个 SQL 参数占位符然后在执行前通过AddWithValue把前端传来的字符串转成 Int32 再赋给参数。这样做的好处是 SQL 注入被彻底挡在门外因为参数值不会参与 SQL 语法解析。常见误写是把前端值直接拼接成DELETE FROM Users WHERE Id id一旦 id 被改成1 OR 11就会出事参数化没有这个问题。参数说明AddWithValue的第一个参数是 SQL 语句里的参数名必须带前缀第二个参数是实际值。Convert.ToInt32(id)会先把字符串转成 int如果前端传了非数字值这一步会抛异常相当于做了一层类型校验。后面的避坑章节里我会讲为什么这里不能用string类型硬接。3. 核心模块拆解增删改查在无刷新下怎么完成3.1 前端模板GridView 列里嵌套 TextBox 和按钮这个项目的核心思路是让 GridView 的普通列变成可编辑状态但又不使用自带的 EditMode。实现方式是在 TemplateField 里直接放 TextBox再放一个“保存”和一个“删除”按钮。页面加载时从数据库查出数据绑定到 GridView表格里的每一行就是一个可输入的表单不需要切换编辑模式。asp:GridView IDgvUsers runatserver AutoGenerateColumnsfalse DataKeyNamesId OnPageIndexChanginggvUsers_PageIndexChanging AllowPagingtrue PageSize10 Columns asp:TemplateField HeaderText姓名 ItemTemplate asp:TextBox IDtxtName runatserver Text%# Eval(Name) % / /ItemTemplate /asp:TemplateField asp:TemplateField HeaderText邮箱 ItemTemplate asp:TextBox IDtxtEmail runatserver Text%# Eval(Email) % / /ItemTemplate /asp:TemplateField asp:TemplateField HeaderText操作 ItemTemplate input typebutton value保存 classbtn-save>function collectRow(btn) { var row btn.parentNode.parentNode; var id btn.getAttribute(data-id); var name row.querySelector(input[id*txtName]).value; var email row.querySelector(input[id*txtEmail]).value; return actionupdateid encodeURIComponent(id) name encodeURIComponent(name) email encodeURIComponent(email); } function post(url, data, callback) { var xhr new XMLHttpRequest(); xhr.open(POST, url, true); xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded); xhr.onreadystatechange function () { if (xhr.readyState 4 xhr.status 200) { callback(xhr.responseText); } }; xhr.send(data); } document.addEventListener(click, function (e) { var target e.target; if (target.classList.contains(btn-save)) { var query collectRow(target); post(AjaxHandler.ashx, query, function (res) { alert(res ok ? 保存成功 : 保存失败); }); } });逻辑说明collectRow先拿到按钮的父级第一个parentNode是td第二个parentNode是tr然后通过querySelector用属性选择器找到行内ClientID包含txtName的输入框读取它的值。返回的字符串就是 POST 请求的 body。post函数里encodeURIComponent是对值做转码防止中文或特殊字符破坏请求格式。整个点击事件绑定在 document 上用的是事件委托这样即使 GridView 分页后重新生成了新的按钮也不用重新绑定事件。这里有一个容易踩的细节Content-Type设置为application/x-www-form-urlencoded是必须的否则服务端context.Request[name]取不到值。我之前在这个地方翻过车一直以为 POST 数据只要发送出去就能取到实际上如果不指定这个头IIS 会把请求体当成普通文本处理取出来的都是 null。3.3 后端处理AjaxHandler 里分发增删改操作AjaxHandler.ashx.cs是整个项目的心脏。它在ProcessRequest方法里读action参数然后分别调用不同的数据库操作方法。这样做的好处是把“HTTP 请求参数解析”和“数据库业务逻辑”分开方法之间互不干扰。public void ProcessRequest(HttpContext context) { context.Response.ContentType text/plain; context.Response.Charset utf-8; string action context.Request[action]; string result fail; switch (action) { case update: result UpdateUser( context.Request[id], context.Request[name], context.Request[email]); break; case delete: result DeleteUser(context.Request[id]); break; case insert: result InsertUser( context.Request[name], context.Request[email]); break; } context.Response.Write(result); context.Response.End(); }逻辑说明ProcessRequest是 .ashx 处理程序的入口方法相当于普通页面里的Page_Load。先设置响应的内容类型为text/plain这样前端收到的就是一个纯字符串不会有多余的 HTML 标签污染判断逻辑。然后根据action参数决定走哪条分支。最后统一用Response.Write输出ok或fail前端收到这个字符串后弹出提示。参数说明action的取值是前端collectRow里拼好的actionupdate或actiondelete。context.Request[id]能够直接读取 POST 表单字段这是application/x-www-form-urlencoded编码方式带来的便利。如果前端改成了传 JSON那这里就要用StreamReader读取请求体再反序列化需要同步修改。3.4 分页排序GridView 自带的回发怎么和 Ajax 共存这个项目比较巧妙的一个设计是增删改走了 Ajax但是分页仍然用 GridView 自带的回发。因为分页回发会把整个 GridView 重新渲染页面其他部分不刷新用户感知上只是表格内容变了没有整页白屏。所以作者没有强行把分页也改成异步而是用了一个比较务实的设计——OnPageIndexChanging里重新绑定数据。protected void gvUsers_PageIndexChanging(object sender, GridViewPageEventArgs e) { gvUsers.PageIndex e.NewPageIndex; BindData(); } private void BindData() { string sql SELECT Id, Name, Email FROM [Users] ORDER BY Id DESC; using (SqlConnection conn new SqlConnection(connStr)) { SqlDataAdapter da new SqlDataAdapter(sql, conn); DataTable dt new DataTable(); da.Fill(dt); gvUsers.DataSource dt; gvUsers.DataBind(); } }逻辑说明分页事件里先把PageIndex设置为新页码然后重新执行查询绑定数据。BindData方法单独抽出来页面第一次加载和分页时共用。注意这里的分页是“假分页”每次查询都取出全表数据到内存GridView 自己只显示当前页的 10 行。如果数据量超过几万条建议改成 SQL Server 的ROW_NUMBER()真分页这个我在二次开发章节里会给出思路。参数说明GridViewPageEventArgs.NewPageIndex是点击页码后目标页码的索引从 0 开始。比如页面上显示“第 2 页”这个值就是 1。gvUsers.PageIndex e.NewPageIndex;必须在DataBind()之前赋值否则你设了新页码又被数据绑定重置回 0。4. 避坑指南跑这个项目时最容易翻车的 5 个点4.1 现象点击保存提示“未结束的字符串常量”第一次运行项目我点保存按钮浏览器直接报错“未结束的字符串常量”。打开调试器看发现前端收集到的数据把名字里的引号带进了弹窗里。原因是用户的姓名里包含单引号比如“奥尼尔”这个字符串拼到alert(保存成功)里就成了alert(奥尼尔)单引号把字符串切断了。原因根子出在collectRow返回的值虽然经过了encodeURIComponent但那只是对 POST 请求体做了编码并没有处理 alert 里的 JavaScript 单引号。解决方法是不要在前端直接拼接弹窗内容而是把返回结果通过JSON.stringify输出或者用简单的转义函数把单引号替换掉。我建议在post的回调里改成alert(typeof res string ? res : 操作失败)并且后端返回的内容固定用ok/fail不带用户数据这样最安全。4.2 现象Ajax 回调后 GridView 不刷新数据还是旧的点击保存提示成功但表格里的数据没有变化。这个问题的原因在于保存成功后前端只是 alert 了一下并没有重新绑定 GridView。你要么手动改 DOM要么强制刷新整个页面。项目源码里用的是“更新成功后调用location.reload()”这种简单粗暴的办法。我在自己的改造中更倾向不发 reload而是让 GridView 重新走一次BindData()。具体做法是前端保存成功后用一个隐藏的__EVENTTARGET触发服务器端重新绑定或者直接在回调里把当前行的 TextBox 值更新为新值。如果只是单条更新直接更新 DOM 最轻量。项目里为了演示简单直接 reload但生产环境里这样做会丢掉用户当前滚动位置这算是一个可以优化的点。4.3 现象IIS 里部署后 .ashx 返回 404本地跑得好好的发布到服务器 IIS 上就 404。原因是 .ashx 文件没有在 IIS 的“处理程序映射”里注册。IIS 6 时代需要在网站属性里给 .ashx 添加通配符映射IIS 7 需要确认“HttpHandler”模块对这个文件类型可见。解决方法是如果用的是 IIS 6在网站属性 - 主目录 - 配置 - 映射里添加.ashx对应C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll。如果是 IIS 7 以上确认项目是以“应用程序”运行的而不是虚拟目录。我踩过这个坑最后发现是因为没有把项目发布成应用程序直接在默认网站下建了一个虚拟目录处理程序映射没继承到。4.4 现象SQL Server 连接字符串报“用户登录失败”修改完 Web.config 里的连接字符串测试连接成功但页面一打开就报“用户 sa 登录失败”。这个多半是连接字符串里写了数据库名但用户没有该数据库的权限。我倾向单独创建一个受限数据库用户只给db_datareader和db_datawriter权限不要用 sa。项目里那个Data.sql脚本执行时也要注意如果脚本里包含CREATE DATABASE GridDemo那必须先保证你有建库权限。最稳的做法是在 SQL Server Management Studio 里用sa或管理员账号执行整个脚本然后把 Web.config 里的连接字符串改成普通账号。另外记得Initial Catalog一定要和脚本里建的库名一致大小写无所谓但拼写错了会报“无法打开数据库”。4.5 现象GridView 分页点击页码后“未将对象引用设置到对象的实例”点击分页页码页面抛异常指向BindData()里的SqlDataAdapter.Fill。看异常堆栈多半是connStr为空或者数据库连接没打开。这里的坑是connStr定义在AjaxHandler.ashx.cs里但BindData是定义在Default.aspx.cs里的两个文件各自读了一遍 Web.config。如果你在Default.aspx.cs里忘了初始化这个字段就会拿空字符串去连数据库。我一般会统一用一个静态类读取连接字符串避免每个文件重复写ConfigurationManager.ConnectionStrings[cnStr].ConnectionString。项目源码里其实每个文件都写了一遍如果你要改连接字符串必须两处都改。5. 部署与配置从下载到跑通的全过程5.1 环境准备VS 版本、IIS Express、SQL Server开始跑这个项目前先把环境准备齐全。我建议的搭配是Visual Studio 2019 社区版 SQL Server 2019 Developer 版本地实例。如果你机器上已经装了 VS2017 也一样项目文件格式是.sln双 击打开即可。不需要额外安装 NuGet 包因为项目没有引用任何外部库。安装 SQL Server 的时候记住实例名比如localhost\SQLEXPRESS或者localhost。如果你是用 SQL Server 2008 的老机器也无所谓项目脚本不依赖新语法。打开 Visual Studio 后在“打开项目”里选.sln文件第一次加载会提示是否信任项目点信任。然后右键项目名选“浏览器查看”IIS Express 就会启动。5.2 数据库初始化执行 Data.sql 脚本在 Visual Studio 的解决方案资源管理器里找到App_Data/Data.sql双击打开然后连接你的 SQL Server 实例执行。脚本内容大致是建一个GridDemo数据库然后建一张Users表插入几条示例数据。这里有个小提醒如果你的 SQL Server 已经是管理员权限直接点“执行”就能建库。如果报权限不足就先用 sa 登录或者让管理员给你CREATE DATABASE权限。CREATE DATABASE GridDemo; GO USE GridDemo; GO CREATE TABLE [Users] ( Id INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Email NVARCHAR(100) NULL, CreateTime DATETIME DEFAULT GETDATE() ); GO INSERT INTO [Users] (Name, Email) VALUES (N张三, Nzhangsanexample.com); INSERT INTO [Users] (Name, Email) VALUES (N李四, Nlisiexample.com); GO逻辑说明IDENTITY(1,1)让主键自增NVARCHAR用来保存中文DATETIME DEFAULT GETDATE()插入时自动记录时间。执行这个脚本后数据库里就有两张示例数据可以直接用来测试增删改。参数说明N张三里的 N 前缀意思是按 Unicode 存储中文不加也能存但加了更规范。你如果想测试分页多插入几条数据就行比如循环插入 100 条。5.3 修改 Web.config 连接字符串与启动调试数据库建好后打开Web.config找到连接字符串这一段connectionStrings add namecnStr connectionStringData Sourcelocalhost;Initial CatalogGridDemo;User IDsa;Password123456; providerNameSystem.Data.SqlClient / /connectionStrings你需要把Data Source改成你的 SQL Server 实例名User ID和Password改成有权限的账号。注意如果这里写错了AjaxHandler 和 Default.aspx 都会报连接错误因为两个页面都会读取这个连接字符串。改完保存直接按 F5 运行。启动后页面会显示一个表格每一行都有输入框和保存、删除按钮。你可以试着修改一个名字点保存然后刷新页面看数据是否更新。如果点了保存没反应按 F12 打开浏览器开发者工具切到 Network 标签看 AjaxHandler.ashx 请求的状态码和响应体。这是排查前端问题最直接的手段。6. 二次开发让表格变成带弹窗确认与批量操作的生产级组件6.1 用自定义模态框替换丑陋的 alert项目里删除按钮直接调用了alert这在开发演示里够了但给客户看的系统里不能这么干。我给这个项目加了一个纯 CSS 模态框点击删除时弹出“确定要删除这条记录吗”和一个“确认”按钮确认后才发 Ajax 请求。核心思路是把删除流程拆成两步>var deleteId null; document.addEventListener(click, function (e) { if (e.target.classList.contains(btn-delete)) { deleteId e.target.getAttribute(data-id); document.getElementById(modal).style.display block; } if (e.target.id confirmDeleteBtn) { if (deleteId) { post(AjaxHandler.ashx, actiondeleteid encodeURIComponent(deleteId), function (res) { document.getElementById(modal).style.display none; if (res ok) location.reload(); }); } } });逻辑说明这个改造没有改变后端接口只是把“发送删除请求”从点击按钮时延迟到点击确认按钮时。好处是误删概率大大降低。注意deleteId是一个全局变量如果用户同时打开多个模态框场景需要改成用按钮实例上的属性来传递避免变量覆盖。这里演示用全局变量足够简单你可以继续优化。6.2 批量删除一行代码收集所有选中行后台管理里经常需要“全选删除”操作。这个项目原生没有这个功能但加上去很容易。给 GridView 加一列 CheckBox然后在前端遍历所有被勾选的行的>case batchdelete: string ids context.Request[ids]; string[] idArr ids.Split(,); foreach (string id in idArr) { // 参数化执行 DELETE FROM [Users] WHERE Id Id ExecuteDelete(Convert.ToInt32(id)); } result ok; break;逻辑说明批量删除的关键是前端把所有选中的 Id 用逗号拼成一个字符串后端Split(,)拆成数组后循环删除。这个实现简单但性能上不如一次性DELETE FROM [Users] WHERE Id IN (...)。你可以用Listint收集后生成Id1,Id2,...这种参数化 SQL一次执行那条路径要考虑参数数量限制但这次循环删除的演示对于几十条数据完全够用。我在实际项目里都会给批量删除加一个二次确认防止误操作。从那以后我每次给项目加表格操作功能都会强制走一遍“前端不拼接 SQL、后端不直接信任参数、每次改动先看请求体和响应体”这套流程踩过一次静态变量和连接字符串重复的坑之后就再没犯过类似的低级错误。这个 GridView 的 Ajax 改造思路无论是学异步交互还是做老项目升级都很值得你下一个源码包亲手试一遍希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑