简介这是一份面向ASP.NET开发者的GridView操作实例基于ASP.NET 4.0与SQL Server 2008环境构建重点解决自带GridView在数据增删改操作中页面频繁刷新、交互反馈生硬的问题适用于后台管理模块、信息发布系统等需要快速搭建表格页面的场景。作者从国外技术论坛筛选出该实用方案代码结构清晰完整涵盖新增、编辑、删除三项核心功能并采用Ajax实现无刷新交互大幅提升操作流畅度同时示例可移植性强稍作调整即可融入现有项目对初中级开发人员尤其友好。资源包共2个文件包括1个htm页面文件和1个rar源码压缩包整体大小约1.52MB其中htm文件主要用于预览运行效果或说明用法rar包内则包含可直接参考的完整代码便于对照学习与实际复用。该资源目前已吸引649人浏览学习在同类GridView示例中属于轻量而实用的收藏级内容。通过这份实例读者可以掌握ASP.NET中GridView结合Ajax进行数据增删改的实现思路理解前后台事件绑定与数据库操作的配合方式并借助现有模板扩展字段和样式有效缩短后台管理页面的开发周期。1. GridView为何能成为ASP.NET的生产力担当1.1 从一次内部系统开发说起去年我在帮一家制造企业做设备资产管理系统时遇到了一个很典型的需求设备台账页面上需要把几百条记录分页展示支持按部门筛选、按状态着色还要能一键导出。按理说这种需求在很多前端框架里都能实现但客户那边是老牌的ASP.NET WebForms环境开发周期只有两周。我第一时间想到的就是把GridView拿出来用。GridView是ASP.NET里最老牌的数据绑定控件之一。它的核心价值在于你不需要手动拼接HTML表格不需要写复杂的循环渲染逻辑只要给它绑定一个数据源设置好列字段它就能自动生成完整的表格结构还自带分页、排序、编辑、删除这些基础交互。对于企业内部系统、后台管理面板这种表格密集型项目这东西省下的时间不是一星半点。说实话这些年我见过不少开发者在技术选型时一听到WebForms就皱眉觉得它老、笨重、控件封装过度。但实际经历过大型项目的人心里都清楚GridView在处理标准CRUD场景时的效率依然很难被替代。尤其是配上AJAX无刷新效果之后用户体验和交互流畅度完全能跟上现代前端的水准。1.2 必须搞懂的几个基础属性要真正用好GridView有几个属性是绕不开的。首先是AutoGenerateColumns默认值是true意思是自动根据数据源的字段生成列。开发阶段这样确实省事但到了生产环境我强烈建议把它设置为false手动声明每个列。原因很简单一旦数据源字段顺序变化或者你只想展示其中几个字段自动生成列会完全失控。其次是DataKeyNames。这玩意儿很多人容易忽略但它直接决定了编辑和删除功能能不能正常工作。GridView的行操作如RowEditing、RowDeleting默认需要通过主键来识别具体是哪一行而DataKeyNames就是用来指定这个主键字段的。我之前碰到过有同事没设置这个属性点击删除按钮之后后台怎么都拿不到行ID排查了半天才意识到是这个小问题。再就是OnRowCommand事件。如果你在模板列里放了自定义按钮比如详情审批这种不在GridView默认操作里的按钮RowCommand事件就是你的主要入口。通过e.CommandArgument可以拿到当前行的主键值再配合CommandName来判断具体点了哪个按钮。这套机制一旦用熟了你就能在GridView上实现各种业务操作而不只是简单的增删改查。2. 给GridView装上AJAX引擎UpdatePanel实战方案2.1 UpdatePanel实现无刷新排序、分页说到GridView的AJAX效果很多初学者第一反应是用jQuery的$.ajax去请求服务端接口然后手动拼接HTML。这个思路本身没错但在WebForms环境里一个更高效的做法是使用UpdatePanel。这是ASP.NET AJAX扩展提供的一个控件它的设计理念是局部回发——整个页面不刷新只更新指定区域的DOM内容。我用一个实际例子来说明。假设页面上有一个GridView显示订单列表开启了排序和分页功能asp:UpdatePanel IDUpdatePanel1 runatserver UpdateModeConditional ContentTemplate asp:GridView IDgvOrders runatserver AutoGenerateColumnsfalse DataKeyNamesOrderID AllowPagingtrue PageSize10 AllowSortingtrue OnPageIndexChanginggvOrders_PageIndexChanging OnSortinggvOrders_Sorting Columns asp:BoundField DataFieldOrderID HeaderText订单号 SortExpressionOrderID / asp:BoundField DataFieldCustomerName HeaderText客户名称 SortExpressionCustomerName / asp:BoundField DataFieldTotalAmount HeaderText订单金额 / asp:BoundField DataFieldCreateTime HeaderText下单时间 / /Columns /asp:GridView /ContentTemplate Triggers asp:AsyncPostBackTrigger ControlIDbtnRefresh EventNameClick / /Triggers /asp:UpdatePanel关键在于后台代码里分页和排序处理事件照常写只是在数据绑定前判断一下IsPostBack就行。当用户点击下一页或者列标题进行排序时页面不会整个刷新只有GridView所在的区域更新。这个过程对用户来说几乎是瞬间完成的体感上跟你用Vue、React这些前端框架做局部刷新没什么区别。2.2 为什么UpdatePanel比纯前端方案更省心我知道有些读者会问放着现成的AJAX不用非要用UpdatePanel这种老古董技术做什么我的回答是要看场景。如果项目已经是Asp.NET WebForms架构后端逻辑大量依赖服务端控件的事件模型比如RowDataBound、RowEditing、ItemUpdating这些生命周期事件那用UpdatePanel是最平滑的升级路径。你不需要改任何后端的业务逻辑只在页面外层包一个UpdatePanel就能获得无刷新的交互体验。这相当于给老项目做微创手术风险低、见效快。而如果强行引入前端框架比如Vue或者React那你就得在服务端设计一套完整的Web API把原有的服务端控件事件全部转换成JSON接口调用再在前端重新实现表格渲染、分页逻辑、校验逻辑。这不是不行但工作量是指数级上升的而且很容易在接口调试、跨域、序列化等环节踩坑。对于企业内部系统来说投入产出比完全不成正比。我之前做过一个对比测试同样一个订单管理页面用UpdatePanel改造大约花了两小时用Vue重写花了整整两天。而且前者在IE和旧版Edge上的兼容性完全没问题后者还得考虑polyfill。不是说前端方案不好而是说工具要匹配场景。3. 进阶玩法JSON交互与自定义数据绑定3.1 用jQuery发AJAX请求把数据喂给GridViewUpdatePanel解决的是服务端控件自身的异步刷新但在实际项目中我们经常需要与第三方接口或自定义业务逻辑交互这时候还是要用原生的AJAX方式。举一个我实际做过案例设备管理系统中前端页面需要根据用户选择的部门异步获取该部门下的设备列表然后展示在GridView中。方案是前端用jQuery发起GET请求服务端返回JSON数据前端遍历JSON并动态拼接GridView需要的HTML结构。服务端的一个精简ASP.NET WebMethod示例[WebMethod] public static string GetDeviceList(string deptId) { DataTable dt GetDeviceDataFromDb(deptId); string json JsonConvert.SerializeObject(dt, new IsoDateTimeConverter { DateTimeFormat yyyy-MM-dd HH:mm:ss }); return json; }这里有一个细节值得说道说道如果直接对DataTable进行序列化DateTime字段默认会变成类似\/Date(1640966400000)\/这样的格式极难阅读。加一个IsoDateTimeConverter就能保证前端拿到的是标准格式的时间字符串。这种细节点文档里很少会写清楚但实际开发中几乎必然会遇到。前端处理返回的数据也不难。拿到JSON数组之后用原生JavaScript或者jQuery拼接出表格行然后渲染到目标容器中。这个方案的灵活性比UpdatePanel高很多适合那些数据结构不固定、需要在前端做高度定制化展示的场景。3.2 服务端从Request.Body读取JSON参数的写法很多开发者在做AJAX请求时习惯用application/x-www-form-urlencoded这种方式传参然后在后台用Request.Form[key]来取值。但如果前端项目用的是application/json格式这个时候Request.Form就取不到值了你需要在后台读取Request.Body。热词里提到的StreamReader(HttpContext.Request.Body)正是处理这种情况的标准写法。我在一个Web API的项目里就踩过这个坑。前端的AJAX请求这样写$.ajax({ url: /api/device/save, type: POST, data: JSON.stringify({ deviceCode: DEV001, deviceName: 注塑机, status: 2 }), contentType: application/json; charsetutf-8, dataType: json, success: function (res) { // 处理结果 } });如果后台是这样取值就会取不到任何数据string deviceCode Request.Form[deviceCode];正确做法是读取请求体string bodyContent; using (var reader new StreamReader(Request.Body, Encoding.UTF8)) { bodyContent reader.ReadToEnd(); } // 然后用JsonConvert.DeserializeObject解析或直接用JObject.Parse var jsonObj Newtonsoft.Json.Linq.JObject.Parse(bodyContent); string deviceCode jsonObj[deviceCode]?.ToString();这里有个容易忽略的问题Request.Body只能读取一次。如果你在过滤器或中间件里先行读取了Body后面再在控制器里用ReadToEnd()就会拿到空字符串。解决方法是读取时定位到流的起始位置Request.Body.Position 0;或者使用EnableBuffering()方法提前启用请求体缓冲。这个坑我印象特别深因为光排查为什么Body读出来是空的就花了大半天时间。4. 常见问题与排查技巧实录4.1 ajax请求设置编码格式中文乱码的根源涉及中文场景的AJAX请求最大的坑就是编码格式不统一。我见过无数开发者在遇到返回中文乱码时第一反应是去改前台页面的charset但问题往往出在服务端。排查乱码问题的顺序应该是这样的先看请求头里的Content-Type是否包含charsetutf-8。如果用的是jQuery默认处理POST请求时会设置application/x-www-form-urlencoded; charsetUTF-8这个一般没问题。但如果用了JSON.stringify并且自己指定了contentType就一定要补上charsetutf-8这后半句。只写application/json的话部分服务器默认按ISO-8859-1来处理响应体中文必乱。再看服务端页面的Response.ContentEncoding。ASP.NET里可以在Page_Load中显式设置Response.ContentEncoding Encoding.UTF8; Request.ContentEncoding Encoding.UTF8;最后检查数据库连接字符串。如果连接MySQL时不加CharSetutf8即使前后端编码都对数据从数据库里拿出来时就已经变成乱码了那前端的排查就全部白费。这三个层级逐个确认下来99%的乱码问题都能解决。4.2 浏览器报invalid url的排查思路热词里有一条是jq ajax syntaxerror: failed to execute open on xmlhttprequest: invalid url这个报错我印象非常深刻。它的含义是XMLHttpRequest对象的open()方法接收到的URL格式不合法。出现这个错误最常见的原因有两类。第一类URL字符串里包含了非法字符。比如项目中用了模板字符串某个变量的值是undefined或者null拼接出来就成了/api/device/undefined。在这个路径下如果服务端路由处理不了或者前端拦截器对这个路径做了特殊处理浏览器就会报invalid url。这类问题的排查思路很简单在发起AJAX请求之前先打印一下URLconsole.log(requestUrl);如果打印出来确实是undefined那就往回找变量的赋值逻辑多半是异步回调里数据还没返回就执行了拼接操作。第二类原因是URL带了非法格式的query参数比如某个参数值里面有空格或中文却没有做encodeURIComponent编码。虽然浏览器一般会自动处理中文但某些边缘字符比如#、%、混在一起就会导致URL解析异常。我的习惯是凡是动态拼接的参数一律先编码再拼URLvar fullUrl /api/device/list?keyword encodeURIComponent(keyword) page pageIndex;4.3 给ajax请求参数赋值时容易踩的坑还有一个高频问题是怎么给AJAX请求的参数正确赋值。很多人在使用$.ajax的时候对于data参数到底应该传对象还是序列化后的字符串拿不太准。这里我想说清楚一个底层逻辑。jQuery的$.ajax在默认情况下如果data传的是对象processData会把它自动转换成key1value1key2value2这种urlencoded格式。但如果contentType设置成了application/json那processData的默认行为会和Content-Type冲突数据格式对不上服务端就解析不出来。所以我的建议是遵循一个组合原则传普通键值对用application/x-www-form-urlencodeddata传对象即可。传嵌套结构或数组用application/jsondata需要JSON.stringify(obj)序列化。还有一种情况也要注意如果后端接口要求的参数名是data你自己又把这个对象赋值给了data服务端序列化时很容易出现属性名冲突的混淆。这种情况命名时要格外小心前后端约定好字段名避免歧义。关于参数赋值我还想提一个服务端的配套写法。ASP.NET的WebMethod静态方法接收JSON参数时参数名一定要和前端传递的key严格一致大小写也不能错。很多前端传的字段是驼峰命名而后端用的是帕斯卡命名或者全小写序列化时就会绑定失败。我见过最快的解决办法是直接用JObject.Parse或者JsonConvert.DeserializeObjectDictionarystring, object来接收这样就绕开了参数名匹配的限制灵活处理各种字段名。5. 实战手记把GridView和AJAX组合成一套完整方案5.1 一个设备台账页面的完整实现流程说了这么多理论层面的东西最后我来完整复盘一下当时给那家企业做的设备台账页面。这个页面综合了GridView、UpdatePanel、jQuery AJAX三种技术能够很好地说明它们在真实项目中是如何分工协作的。页面逻辑是这样的顶部是搜索区和部门筛选下拉框中间是GridView设备列表支持分页、排序、状态高亮最右侧有一个同步状态按钮点击后通过AJAX调用后端接口动态更新设备运行状态。GridView部分仍然放在UpdatePanel里负责处理分页、排序这些服务端事件。搜索和筛选通过AsyncPostBackTrigger触发UpdatePanel的回发。这样用户切换部门、翻页、排序时体验都是无刷新的。而同步状态这个功能则是一次独立的jQuery AJAX请求请求后端一个专门处理状态同步的接口。接口返回最新的设备状态数据之后前端用$.each遍历JSON在DOM中动态更新对应行的状态标签。这里有一个很重要的设计思路**粗粒度的交互操作交给UpdatePanel细粒度的数据交互交给jQuery AJAX。**两者各司其职而不是试图用一种技术解决所有问题。这是我在多个项目中总结出来的经验——试图用UpdatePanel做精细化的DOM操作很别扭用纯AJAX做表格分页排序又太浪费人力组合拳才是最优雅的方案。5.2 分页性能优化与ViewState管理的经验最后想分享一个关于性能的细节。GridView使用时会默认启用ViewState这会导致页面上有一个很大的隐藏字段存储了表格状态。如果数据量大、列数多这个隐藏字段的大小可能达到几十KB甚至上百KB严重影响页面加载速度。优化的思路是对于不需要在回发后保持状态的列可以关闭ViewState对于整个GridView如果不需要在服务端事件中获取原始数据可以设置EnableViewStatefalse。但要注意关闭ViewState之后RowCommand事件中通过e.CommandArgument拿主键的方式仍然有效因为数据是存在DataKeyNames里的。不过如果涉及编辑操作需要把整个行的数据都回传那就不能轻易关掉ViewState需要权衡取舍。在实际项目中我的做法是只读展示的GridView直接关闭ViewState配合DataKeyNames存储主键这样既保障了事件回调的数据支撑又显著减小了页面体积。如果必须支持行内编辑就把编辑操作改成弹窗形式通过AJAX传一整行的主键和修改字段而不是依赖GridView的默认编辑模式。这样既保留了良好的交互体验又绕开了ViewState的性能问题。5.3 一些实用的前端配合技巧既然说到了这里再补充几个GridView和华前端配合上手就能用的小技巧。一个是状态高亮。我们经常需要在设备状态异常时高亮显示该行在RowDataBound事件里判断状态值动态添加CSS类即可。这个写法非常直接protected void gvDevices_RowDataBound(object sender, GridViewRowEventArgs e) { if (e.Row.RowType DataControlRowType.DataRow) { string status DataBinder.Eval(e.Row.DataItem, Status).ToString(); if (status 2) // 异常状态 { e.Row.CssClass row-danger; } } }另一个是列宽与省略号的适配。GridView在窄屏下撑破布局是常见问题。CSS里给表格设置table-layout: fixed给需要截断的列设置text-overflow: ellipsis; overflow: hidden; white-space: nowrap;再配合标题提示整体效果就很规整了。还有遇到过一个很实用的小技巧。GridView导出Excel时直接设置Response.ContentType application/vnd.ms-excel再把GridView渲染到HtmlTextWriter里输出即可。这个方法不需要引入第三方组件代码量也很少和前端AJAX交互也能天然兼容因为整个导出过程是服务端完成的不涉及页面回发。在做这几个功能时我最深的体会是技术本身没有什么新旧之分关键是看它解决什么样的业务场景。GridView这套组合方案虽然老但在企业级信息化系统里依旧非常能打。它让我省下了大量处理表格交互细节的时间把精力放在了业务流程和用户体验上。这也是为什么我每次做WebForms项目时总是第一时间把这套组合方案排上日程。本文还有配套的精品资源点击获取