ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GridView无刷新改造:从UpdatePanel到PageMethods的ASP.NET AJAX实践

GridView无刷新改造:从UpdatePanel到PageMethods的ASP.NET AJAX实践 简介这是一份面向ASP.NET开发者的GridView实用实例尤其适合正在使用VS自带GridView控件、希望实现无刷新增删改操作的中级程序员。资源基于ASP.NET 4.0与SQL Server 2008环境通过Ajax技术解决了默认控件交互单一、刷新频繁的痛点整体设计简洁却功能完整。压缩包共2个文件主要包含一个htm格式的使用说明页面和一个rar格式的源代码包整体体积仅1.52MB轻量便携。目前已有649人学习下载口碑可见。该实例的可移植性很强代码结构清晰事件处理逻辑完整可直接嵌入现有信息系统实现列表展示、分页、编辑、删除等常见功能同时避免页面刷新提升操作体验与开发效率。对需要快速搭建后台数据管理模块的开发者来说是一份值得收藏的参考代码后续也可根据业务需求灵活扩展。1. 为什么GridView默认体验让人头大——回发的锅先说个几乎每个ASP.NET老手都经历过的场景你费了半天劲把数据表绑定到GridView上列头一点就能排序底部还有页码可以翻功能确实齐全。结果一部署到现场用户点了一下下一页整个页面刷地一下白屏再重新渲染滚动条回到顶部填了一半的表单内容全没了。用户当场问你这网站怎么这么卡问题不在GridView本身而在WebForms的整个回发Postback机制。GridView的排序、分页、删除这些内置操作本质都是把整个页面表单提交回服务器服务器跑一遍完整的页面生命周期再把整张HTML重新发送回浏览器。也就是说哪怕你只是想翻个页浏览器也要重新下载整个页面的所有HTML、CSS、JavaScript这个体验在局域网里还能勉强接受一旦用户网络状况一般或者页面本身比较大那种“跳一下”的割裂感非常劝退。更让人难受的是ViewState还会把整个页面的状态都塞进一个巨型隐藏字段里数据量大一点这个hidden input可能比页面本身的HTML还大来回传输全是额外开销。所以标题里说的“非常好用”关键不在GridView控件本身而是指如何在GridView上叠加AJAX效果把这种糟糕的体验彻底改掉。我最早做这个改造的时候试过两条路线一条是官方提供的UpdatePanel改动最小几分钟就能让GridView无刷新翻页另一条是彻底绕开WebForms生命周期用PageMethods加前端渲染体验最丝滑但工作量也更大。这两条路我都实际跑过不少项目各自的适用场景和完善方案下面分开讲。2. 先上一个最省事的方案UpdatePanel轻量改造如果你手头是一个维护中的老项目代码里到处都是服务端事件和ViewState依赖那我建议你第一步先上UpdatePanel。这条路的精髓就是控件树不用动事件逻辑不用动前端拿到的还是完整回发的行为只是由UpdatePanel帮你拦下来用异步请求偷偷提交回服务器再局部刷新页面上某一块区域。2.1 最小改造配置UpdatePanel的最小配置就三个要素ScriptManager、UpdatePanel、ContentTemplate。把GridView整体包进ContentTemplate里就行。看下面的例子asp:ScriptManager IDScriptManager1 runatserver EnablePartialRenderingtrue / asp:UpdatePanel IDgridPanel runatserver UpdateModeAlways RenderModeBlock ContentTemplate asp:GridView IDgvData runatserver AutoGenerateColumnsfalse AllowPagingtrue PageSize10 OnPageIndexChanginggvData_PageIndexChanging OnSortinggvData_Sorting AllowSortingtrue / /ContentTemplate /asp:UpdatePanel后端事件照常写跟没有UpdatePanel时一模一样protected void gvData_PageIndexChanging(object sender, GridViewPageEventArgs e) { gvData.PageIndex e.NewPageIndex; BindData(); } private void BindData() { DataTable dt GetDataFromDb(); // 实际项目里建议用ORM或者SqlDataAdapter gvData.DataSource dt; gvData.DataBind(); }就这么多。部署上去之后你再点翻页页面顶部不会刷新滚动条纹丝不动后台确实发起了一个异步回发但用户无感知。2.2 UpdateMode的选择直接决定请求频率UpdatePanel的UpdateMode有Always和Conditional两个值很多人直接忽略这个属性默认用Always结果页面上放了两个UpdatePanel其中一个的按钮点击之后所有面板全都刷新一遍。自我反省一下我一开始也这么干过因为懒。Always的意思是页面上任意一个UpdatePanel触发了异步回发则页面上所有UpdatePanel都会跟着刷新。Conditional则更精细只有发生异步回发的那个UpdatePanel或者代码里显式调用UpdatePanel.Update()的面板才会刷新。用Conditional再配合ChildrenAsTriggersfalse你可以做到一个GridView翻页只刷新GridView区域其他区域的图表、统计值完全不动性能差距在数据量大了以后非常明显。2.3 UpdatePanel不能解决的三个实际问题UpdatePanel虽然改动最小但有些问题它天生管不了。首先它传输的ViewState数据量不会变少。这个隐藏字段照样包含GridView的所有状态数据行多时照样几百KB来回传。其次如果你在GridView的RowCommand里需要用AJAX弹层或者触发一个客户端的JavaScript函数做一些复杂的页面操作UpdatePanel帮不上什么忙。最后如果GridView放在一个ASP.NET的ContentPage母版页里主页面和内容页各有一个ScriptManager冲突这种部署问题很折腾人。后面有单独一节专门讲这个坑。所以我现在的建议是老项目紧急优化先上UpdatePanel。新项目或者对交互体验要求高的页面直接跳过它走PageMethods方案。3. 想要真正的无刷新体验PageMethods前端渲染才是正解我一直觉得WebForms被很多人嫌弃很大程度上不是因为控件不好用而是因为它把“服务端负责渲染HTML”这条路走得太极端导致前端已经进入MVVM时代了后端还在用字符串拼接表格。实际上ASP.NET完全支持只把数据JSON返回给前端由前端的模板引擎去拼表格HTML。GridView只做数据载体不做HTML渲染这样AJAX效果最干净。3.1 服务端只留一个WebMethod在ASP.NET WebForms里用PageMethods实现这个效果非常直接。先在后端页面代码中开一个公共静态方法加上[WebMethod]特性[System.Web.Services.WebMethod] public static object GetGridData(int pageIndex, int pageSize, string sortExpression) { // 这里做数据查询返回JSON-friendly对象 var list GetPagedDataFromDb(pageIndex, pageSize, sortExpression); return new { rows list, total GetTotalCount() }; }需要注意WebMethod必须是public static不能访问页面实例的控件和ViewState所以数据查询逻辑不能依赖服务端控件状态一切参数都得从前端传过来。这个约束其实就是好事——逼着你把页面状态管理放到前端前后端完全解耦。开启支持也简单在ScriptManager里加一行EnablePageMethodstrue。asp:ScriptManager IDScriptManager1 runatserver EnablePageMethodstrue /3.2 前端jQuery接管渲染前端就自由很多了。我用jQuery发起请求拿到JSON之后拼表格行翻页排序统统自己控制。先搭一个空的table结构放在页面上只留表头table classtable table-hover idgridTable thead tr th>function loadData(pageIndex) { $.ajax({ type: POST, url: OrderList.aspx/GetGridData, data: JSON.stringify({ pageIndex: pageIndex, pageSize: 10, sortExpression: currentSort }), contentType: application/json; charsetutf-8, dataType: json, success: function(response) { var result JSON.parse(response.d); renderTable(result.rows); renderPager(result.total); }, error: function(xhr, status, error) { console.error(加载失败:, error); alert(数据加载失败请稍后重试); } }); }这里有一个WebForms老特性容易踩坑WebMethod默认返回的JSON外层包了一层.d也就是response.d字段。这是ASP.NET AJAX的一个历史包袱从前一直有。掌握了这个规律解析数据就明白了。renderTable和renderPager的实现不复杂用字符串拼接或者模板引擎都行。我个人建议用ES6的模板字符串拼比当年用字符串加号拼接舒服太多function renderTable(rows) { var html ; for (var i 0; i rows.length; i) { var r rows[i]; html tr td${r.OrderId}/td td${r.CustomerName}/td td${r.Amount}/td td${r.CreateTime}/td tda href# onclickviewDetail(${r.OrderId});return false;查看/a/td /tr; } $(#gridTable tbody).html(html); }这种方案改完之后你再看那个页面翻页只是局部表格内容变了URL不带任何回发参数接口可以被缓存前端可以随时替换成Vue或React都不影响后端。交互体验和现代前端框架做出来的页面基本没有差别了。3.3 前后端分离边界怎么划分有人会问这样写是不是等于把GridView扔了我的看法是GridView的数据能力还在后端只是你不再用它生成HTML了。很多人担心这样一来代码量会不会变大。我的答案是如果你只做一两个页面确实大一点。但如果你把loadData、renderTable、renderPager这些函数抽成公共的grid.js组件后面每个页面只需要配置几个参数新增一个列表页也就是半小时的事比在服务端控件上改来改去还省心。整套逻辑可复用性非常强。4. 分页、排序、行内操作这些事件怎么配合AJAX玩前面讲了PageMethods的基础框架但实际的GridView列表页不可能只有翻页。排序、行内按钮、批量选择、弹窗编辑这些操作都要在前端做出来。这一节我把这些细节单独拎出来讲。4.1 排序点表头重新请求GridView默认排序是点表头触发服务端排序事件在PageMethods方案里这个行为要自己接管。我的做法是监听表头th的click事件读取data-sort-field属性然后切换排序方向aesc/desc再重新loadData。$(#gridTable thead th[data-sort-field]).on(click, function() { var field $(this).data(sort-field); if (currentSort field _asc) { currentSort field _desc; } else { currentSort field _asc; } loadData(1); // 排序后回到第一页 });后端接收到sortExpression之后在SQL或者ORM查询里拼上ORDER BY字段即可。注意字段名不要直接拼接用户输入否则容易写出SQL注入漏洞。安全写法是做一个字段白名单映射合法字段才拼进SQLvar sortWhitelist new Dictionarystring, string { { OrderId, OrderId }, { CustomerName, CustomerName }, { Amount, Amount }, { CreateTime, CreateTime } }; if (!sortWhitelist.ContainsKey(sortField)) { throw new Exception(非法排序字段); } string sqlSort sortWhitelist[sortField] (sortDir desc ? DESC : ASC);4.2 行内操作RowCommand迁移到前端事件GridView时代的RowCommand是一个服务端事件在PageMethods方案里自然就没了。行内操作按钮统一变成前端事件函数。比如“删除”按钮先弹确认框确认后AJAX调用后端WebMethod执行删除最后刷新当前页数据。这个流程比RowCommand更符合现在前端交互习惯。function deleteRow(orderId) { if (!confirm(确认删除该订单)) return; $.ajax({ type: POST, url: OrderList.aspx/DeleteOrder, data: JSON.stringify({ id: orderId }), contentType: application/json; charsetutf-8, dataType: json, success: function(response) { if (response.d) { loadData(currentPage); // 删除后刷新当前页 } else { alert(删除失败请稍后重试); } } }); }后端WebMethod里就是普通的删除逻辑。有一个细节值得注意如果当前页只有一行数据删除完后total减少了一页loadData(currentPage)会请求一个不存在的页码返回空数据。这种情况要判断一下如果返回数据为空且当前页码大于1则pageIndex减一if (result.rows.length 0 currentPage 1) { currentPage - 1; loadData(currentPage); } else { renderTable(result.rows); renderPager(result.total); }这个“删光本页最后一行自动往前翻一页”的需求在GridView时代是PageIndex PageCount的判断在AJAX方案里就是这一行front-end兜底逻辑。细节虽小但实际用户很容易碰到。4.3 不用GridView控件后状态管理的习惯要变这里必须记一个重要转换认识服务端控件时代状态主要交给ViewState前端没那么多心智负担。切换到PageMethods后当前页码、排序字段、筛选条件这些状态全部要挂在前端变量里。我吃过亏的是页面一旦发生回发比如页面上还有其他asp:Button触发了普通postback前端变量全部丢失列表会重置回第一页。解决办法有两个方向一是次要按钮都改成异步回发或者纯前端操作尽量避免整页postback二是页面加载的时候把初始筛选条件放在页面隐藏字段里回发后JavaScript重新读取并恢复状态。如果项目里实在避免不了其他普通回发控件优先判断一下这些控件是否也能改成AJAX通常都能改。5. 实战中踩过的坑与排查思路这条路我走过多遍踩坑无数。下面这几个坑具有代表性每个都值得记录一下排查过程后面再有类似问题可以直接定位。5.1 ScriptManager冲突ContentPage与母版页的死对头这个坑很多做WebForms的人都会碰到页面用了Site.master母版页母版页里放了一个ScriptManager用于全站AJAX初始化内容页里想再放一个ScriptManager来开启EnablePageMethods结果一运行直接报错“ScriptManager只能在页面上注册一次”。最初的排查思路是这样的我先看报错堆栈是脚本管理冲突然后去查是否多个ScriptManager同时存在。母版页的是肯定的内容页的是我加的那问题就在这里。但根本原因不是“多个ScriptManager”而是ContentPage的AJAX脚本管理必须是母版页里那个。最终解决办法是把EnablePageMethodstrue直接写在母版页的ScriptManager上或者在内容页里通过代码获取母版页的ScriptManager引用再编程方式设置var sm ScriptManager.GetCurrent(Page); sm.EnablePageMethods true;如果你不想全站页面都开启PageMethods这个编程方式也能在Page_Load里面按需控制更灵活。这个坑提醒我设计母版页时ScriptManager这种全局组件一定提前规划好别等具体功能做的时候再加容易引发连锁问题。5.2 UpdatePanel失效事件触发了但页面还是刷新这个排查过程比较典型。之前有个页面GridView放在UpdatePanel里但点击一个LinkButton触发RowCommand后页面还是整页刷新UpdatePanel完全没生效。排查链路是这样的先看Network面板发现请求不是XHR异步回发而是普通POST回发说明ScriptManager的异步脚本没有接管这个LinkButton。再查页面源码发现这个LinkButton并没有生成ScriptManager的OnClick异步脚本标记。经过一顿搜索问题定位在LinkButton的OnClientClick里头有复杂的JavaScript逻辑其中有一处调用__doPostBack()混用了两种回发方式导致ScriptManager的事件代理没拦截住。解决办法是去掉OnClientClick里的手动__doPostBack改成在服务端事件里输出注册脚本或者在事件后手动调用UpdatePanel.Update()。这种问题的排查思路核心就一条先确认请求是异步还是同步再反过来找是哪个控件绕过了ScriptManager。5.3 WebMethod返回的中文乱码与JSON序列化WebMethod的返回编码一般不会出问题但有些老项目为了兼容IE设置了页面编码为gb2312注意这里我指的是老项目的兼容性做法不是在推荐某种编码就会出现WebMethod返回的中文变成乱码的情况。此时最好的办法是给WebMethod的返回统一加一个JSON序列化设置比如用Newtonsoft.Json指定UTF-8编码[System.Web.Services.WebMethod] public static string GetGridData(int pageIndex, int pageSize, string sortExpression) { var list GetPagedDataFromDb(pageIndex, pageSize, sortExpression); return Newtonsoft.Json.JsonConvert.SerializeObject(new { rows list, total GetTotalCount() }); }返回string而不是object前端再JSON.parse一次。这样做的好处是绕开了ASP.NET AJAX自带序列化在某些编码配置下不稳定问题而且Newtonsoft.Json对循环引用和DateTime格式的控制更可靠。前端那边记得把contentType设置成application/json; charsetutf-8保证请求体编码正确。5.4 ViewState累积导致请求越来越大UpdatePanel方案虽然没有整页刷新但ViewState还是会随着GridView数据行数累积。某次运维反馈说有个列表页越用越卡一看ViewState字段的值已经有几百KB了。排查下来发现Gridview里面塞了很多模板列每列还有一些复杂的控件状态。解决办法很简单把GridView的EnableViewState设为false然后确保每次Page_Load都重新绑定数据。如果你每个操作都主动重新绑定ViewState确实没什么大用反而拖累性能。不过要特别注意设了EnableViewStatefalse之后如果只在IsPostBackfalse时才绑定数据翻页时数据就丢了。所以正确姿势是每次都绑定或者用可缓存的数据源在回发时快速重新查一遍代码简洁和性能都可接受。5.5 前端拿到response.d但数据为null这是PageMethods一个经典的“首次点击成功第二次点击突然null”的怪问题。排查过程中我发现第二次请求时后端WebMethod确实执行了但返回的结果里d为null。这种问题的根因通常是后端返回的匿名对象里有某个属性无法被JSON序列化比如含有循环引用的实体对象或者DateTime格式在一些配置下抛了异常。解决方式是我前面说的——避免直接用匿名对象包裹EF实体先Select成DTO再序列化绕开实体框架代理类的循环引用var dtoList list.Select(x new OrderDto { OrderId x.OrderId, CustomerName x.CustomerName, Amount x.Amount, CreateTime x.CreateTime.ToString(yyyy-MM-dd HH:mm:ss) }).ToList();遇到d为null第一步先不要看前端把网络请求里的响应内容拿到浏览器控制台里看具体是什么字符串再判断是序列化失败还是返回了null数据。这个思路会让你省下很多时间。6. 把GridView封装成通用组件才能真正“好用”前面讲的都是单页面改造。如果只改一个页面那写起来确实很随意。但当你手上有十几个列表页都需要无刷新体验的时候再一遍一遍复制代码就非常痛苦了。所以真正“非常好用”的GridView方案一定是将公共逻辑抽成可复用组件的。我的做法是写一个公共的grid.js把loadData、renderTable、renderPager、排序事件绑定、删除按钮等全部封装进去。每个页面只需做三件事在页面上定义一张带data属性的table框架表头th的data-sort-field字段、tbody、分页容器。配置一个全局对象告诉我grid.jsAJAX的URL也就是当前页面的aspx路径、WebMethod方法名、每页大小、需要渲染的列绑定。页面加载时调用一次initGrid()。实际使用时一个列表页的JavaScript往往只有十几行配置代码阅读和维护成本非常低。这个封装思路对团队协作特别有利新同事接手列表页开发时不需要理解WebForms完整生命周期只需要看grid.js的文档和既有页面的配置示例就能很快上手。这种“把复杂留给基建把简单留给业务”的做法正是GridView在ASP.NET应用里保持“好用”的最佳路径。我自己在多个项目中用这套方案替换了老旧的UpdatePanel逻辑页面响应速度提升非常明显特别是大数据量场景下不再有页面卡顿和滚动条跳动的问题。如果读完这篇文章后你正在维护一个老WebForms项目我的建议是先从UpdatePanel改造入手等业务和团队适应了无刷新的交互模式再逐步挑战PageMethods方案一次一个列表页地迁移。这样风险和收益都比较可控。本文还有配套的精品资源点击获取
返回列表