ARTICLE DETAIL

资讯详情

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

C#+EasyUI后台管理系统开发:从布局到数据交互的完整实践

C#+EasyUI后台管理系统开发:从布局到数据交互的完整实践 简介面向ASP.NET MVC开发者的C#与EasyUI综合示例基于Visual Studio 2013和SQL Server 2014构建演示后台管理系统中最常见的增删改查、分页、Excel导出与图片上传流程。项目通过EasyUI完成页面整体布局与表格组件渲染将前端事件与后端控制器方法联动使读者能够准确理解从界面操作到数据持久化的完整链路。分页部分默认采用SQL Server 2012的OFFSET/FETCH关键字写法同时提供getPage2005方法兼容2005/2008版本开发者可根据现有数据库环境灵活切换这种兼容处理对维护老系统的团队尤其实用。压缩包为28.54MB的rar文件包含可编译的源码工程及页面样式与脚本目录组织清晰便于在Visual Studio中直接打开、调试并提取复用。目前已有218人学习下载适合希望快速掌握EasyUI布局与C#后端整合的初中级开发者也适合需要在前台交互与后端数据操作之间建立完整认识的Web工程师。1. 用C#EasyUI搭管理系统后台吃透能搬走的模块C#EasyUI的组合在企业的内部管理系统里出现频率很高用户管理、订单录入、库存台账交给一套后台就能跑。EasyUI负责前端页面DataGrid表格、Layout布局、Dialog弹窗点几下就能把操作区做出来C#这边用一般处理程序ashx接Ajax返回JSON处理新增、修改、删除、导出Excel、上传文件。拆这套资源时我的感受是它没有引入什么复杂框架核心是一个可复用的骨架——你改字段名、换数据表就能搬到下一个项目。适合刚开始做管理后台、想绕开前后端分离复杂度的开发也适合在公司老系统上做维护加功能的场景。下面我按前端布局、后端增删改、Excel导出、文件上传四个模块拆开讲把参数和坑一次说清。2. EasyUI布局与DataGrid搭建页面主框架2.1 Layout布局Left菜单加Center内容区的组成方式EasyUI的布局组件Layout是我拆这套资源时第一个看的点。它把页面分成north、south、east、west、center五个区域后台系统通常只需要west放菜单、center放页面主体。用EasyUI的layout不会像自己写CSS那样在浏览器宽度变化时出现浮动错位而且带内置的折叠按钮用户能自己收缩左侧菜单这是很多老旧iframe后台没有的体验。实际页面里我先在HTML的body上挂一个classeasyui-layout然后指定三个div块分别对应north、west、center。north放顶部栏和退出按钮west放菜单手风琴center放一个Tab容器。初始代码大致是这样div classeasyui-layout stylewidth:100%;height:100%; div>$(#dg).datagrid({ url: /Handlers/UserHandler.ashx?actionquery, method: post, pagination: true, pageSize: 20, pageList: [10, 20, 50], columns: [[ { field: ck, checkbox: true }, { field: UserId, title: ID, width: 60 }, { field: UserName, title: 用户名, width: 140 }, { field: DeptName, title: 部门, width: 120 }, { field: CreateTime, title: 创建时间, width: 160, formatter: formatTime } ]] });pagination: true打开后后台接口必须自己解析 page 和 rows 两个参数。我通常在 Handler 里这样接收int page Convert.ToInt32(context.Request[page]); int rows Convert.ToInt32(context.Request[rows]);然后拼到 SQL 的OFFSET Offset ROWS FETCH NEXT Rows ROWS ONLY。老项目里很多人还在用TOP加NOT IN翻页数据量过万后性能会明显下降SQL Server 2012 及以上直接换 OFFSET 写法参数化以后也不怕注入。DataGrid 的 method 建议显式设成 post。默认 GET 在查询条件带中文或特殊字符时容易出问题POST 会省掉一大部分转义。查询时把条件塞给 DataGrid 要用$(#dg).datagrid(load, { keyword: $(#kw).val() })不要自己改 url 上的 query string。load 方法会保留原有 url 只追加参数如果你改成拼 url分页时条件很容易丢翻到第二页就查不回第一页的数据。另外列定义里的 formatter 经常被忽视。C# 传回来的时间字符串一般是2025-06-11T14:33:00不格式化直接显示很难看我在 common.js 里统一放 formatTime 函数处理值和整行数据一起传回页面上所有时间列都用同一个函数样式就统一了。2.3 Toolbar与表单弹窗把增删改按钮接到对话框上DataGrid 的操作按钮一般放在 toolbar 里。这套资源的做法是 toolbar 放一个独立 div里面放几个 linkbutton新增、修改、删除、导出、上传。点击时统一走一个分发函数用按钮的 id 或 onclick 区分动作。div iddg-toolbar a classeasyui-linkbutton>public static void WriteJson(HttpContext context, bool success, string msg, object data null) { var result new Dictionarystring, object { { code, success ? 0 : 1 }, { msg, msg }, { data, data } }; string json Newtonsoft.Json.JsonConvert.SerializeObject(result); context.Response.ContentType application/json; context.Response.ContentEncoding Encoding.UTF8; context.Response.Write(json); }Newtonsoft.Json是经典序列化库.NET Core 以上项目可以用内置的System.Text.Json但 ASP.NET WebForms 的 ashx 环境里 Newtonsoft 仍然最常见。这里特别要注意ContentEncoding不设 UTF-8 时中文在部分浏览器下会乱码。code统一用 0 表示成功1 表示失败前端只跟 0 比较比success: true/false更通用后续对接第三方系统时数字码更好映射。Handler 入口写法也要统一。我在 ProcessRequest 开头拿一个 action 参数再 switch 分发。action 的取值和前端 url 上的参数一致比如query、save、delete。这样每个 Handler 对应一张业务表所有操作走同一个文件不用为每个功能建新 ashx文件数量可控排查问题也快。异常处理必须兜底ProcessRequest 外层包 try-catchcatch 后记录异常并返回 code1而不是让 ASP.NET 吐黄页否则前端 ajax 解析一堆 HTML 直接挂掉。3.2 新增与修改一个接口两分支的传参约定新增和修改在大多数业务里是同一个表单区别只在主键。为了不写两套接口我让 save 动作同时承担两种职责主键字段为空就 INSERT否则 UPDATE。这个模式在 C# 里实现很直接但要注意参数顺序和类型转换。public void Save(HttpContext context) { string id context.Request.Form[UserId]; string userName context.Request.Form[UserName]; string deptId context.Request.Form[DeptId]; string sql; SqlParameter[] parameters; if (string.IsNullOrEmpty(id)) { sql INSERT INTO Sys_User (UserName, DeptId, CreateTime) VALUES (UserName, DeptId, GETDATE()); parameters new SqlParameter[] { new SqlParameter(UserName, userName), new SqlParameter(DeptId, deptId) }; } else { sql UPDATE Sys_User SET UserName UserName, DeptId DeptId WHERE UserId UserId; parameters new SqlParameter[] { new SqlParameter(UserName, userName), new SqlParameter(DeptId, deptId), new SqlParameter(UserId, Convert.ToInt32(id)) }; } SqlHelper.ExecuteNonQuery(sql, parameters); }Convert.ToInt32(id)有隐含风险前端传了非数字字符串会抛异常。我在实际项目中先做一次int.TryParse失败就返回“参数不合法”不让用户看到黄页。参数化查询是必须的不要因为是内网系统就拼字符串我遇到过把用户名直接拼进 SQL 导致语法错误的案例。执行结果要看影响行数而不是只看有没有异常UPDATE 一个不存在的 UserId影响行数是 0接口应该返回“记录不存在”不能假装成功。保存动作是否涉及多张表决定要不要事务。单表更新时影响行数已经足够同时写主表和日志表时才需要TransactionScope包一层。日志表我之前吃过亏只更新主表不写操作日志上线后出了问题完全没法追溯。现在凡是做保存我都会顺手把变更前后的关键字段写入操作日志表这个习惯后面排查问题特别有用。3.3 删除与批量删除ids参数与SQL安全处理删除操作形态比保存简单但坑都在参数传递上。DataGrid 开了 checkbox 后用户勾选多行前端拿到选中行数组把所有主键用逗号拼成一个字符串传给后端。最容易翻车的点是用户没选任何行就点删除前端必须先拦截。public void Delete(HttpContext context) { string ids context.Request.Form[ids]; if (string.IsNullOrWhiteSpace(ids)) { WriteJson(context, false, 请先选择要删除的记录); return; } var idList ids.Split(,) .Where(x int.TryParse(x, out _)) .Select(int.Parse) .ToList(); if (idList.Count 0) { WriteJson(context, false, 删除参数不合法); return; } string placeholders string.Join(,, idList.Select((_, i) id i)); string sql $DELETE FROM Sys_User WHERE UserId IN ({placeholders}); var parameters idList .Select((id, i) new SqlParameter(id i, id)) .ToArray(); SqlHelper.ExecuteNonQuery(sql, parameters); WriteJson(context, true, 删除成功); }逗号拼字符串再拆这是最常用做法。string.Join动态生成占位符每个 id 都是独立参数不会被截断也不会拼进 SQL 主体。Split后加int.TryParse过滤目的是防止前端多传一个空字符串导致解析异常。批量删除建议卡上限比如超过 200 条直接提示“请分批删除”避免一次性生成过多参数导致数据库执行超时。删除动作要不要软删除取决于业务。用户表、订单表这类需要留痕的数据我通常加IsDeleted字段删除实际变成 UPDATE。这套资源的处理是物理删除适合日志类数据你在迁移到自己项目时数据层接口要留好扩展位不然以后想改软删除所有 SQL 都翻一遍。前端删除按钮的点击处理要注意DataGrid 没有直接暴露“选中行”的监听但可以用onCheck和onCheckAll维护一个数组删除时读数组。不要点击时getSelections弹确认框确认后再取一次这中间状态一变取到的行就错了。我习惯点击删除时先取选中的主键存到临时变量确认回调里读临时变量。4. 导出ExcelNPOI与下载流程4.1 为什么选NPOI对比Excel COM的三个现实问题C# 导出 Excel 有多种方案传统做法是服务器装 Office再用 COM 组件创建 Excel 对象。这套资源的导出用的不是 COM而是 NPOI。拆的时候我特意确认了这个选择理由很现实服务器上装 Office 本身有授权问题COM 组件在服务进程里操作 Excel 经常出现进程挂起最后只能靠重启 IIS 解决。NPOI 是纯粹类库不依赖 Office生成的 xls/xlsx 文件本质就是一个 ZIP 包稳定得多。NPOI 通过 NuGet 引用旧 .NET Framework 项目里Install-Package NPOI然后using NPOI.SS.UserModel;、using NPOI.XSSF.UserModel;。NPOI 同时支持 HSSFxls最多 65536 行和 XSSFxlsx最多 104 万行。绝大多数后台导出数据在几千到几万行两种格式都覆盖但表超过 6 万行时 xls 会直接报错从代码层面就没必要支持 HSSF 了。另外NPOI 的样式设置比 COM 麻烦一点但胜在结果可预期不会出现“开发机正常、服务器上导出失败”的玄学问题。4.2 服务端生成Excel表头样式、列宽和日期单元格导出功能我建议走独立 Handler参数和查询页一样动作名换成 export。这样前端导出时不用构造复杂参数只需要把当前查询条件原样传给导出接口。服务端流程是查数据 - 创建 Workbook - 创建 Sheet - 写表头 - 写数据行 - 设置列宽 - 输出文件流。public void Export(HttpContext context) { DataTable dt GetUserListForExport(context); // 按条件查询数据 IWorkbook workbook new XSSFWorkbook(); ISheet sheet workbook.CreateSheet(用户列表); IRow headerRow sheet.CreateRow(0); string[] titles { ID, 用户名, 部门, 创建时间 }; for (int i 0; i titles.Length; i) { headerRow.CreateCell(i).SetCellValue(titles[i]); } for (int r 0; r dt.Rows.Count; r) { IRow row sheet.CreateRow(r 1); row.CreateCell(0).SetCellValue(Convert.ToInt32(dt.Rows[r][UserId])); row.CreateCell(1).SetCellValue(dt.Rows[r][UserName].ToString()); row.CreateCell(2).SetCellValue(dt.Rows[r][DeptName].ToString()); ICell dateCell row.CreateCell(3); dateCell.SetCellValue(Convert.ToDateTime(dt.Rows[r][CreateTime])); dateCell.CellStyle GetDateStyle(workbook); } for (int i 0; i titles.Length; i) sheet.SetColumnWidth(i, 20 * 256); WriteExcelToResponse(workbook, 用户列表.xlsx); }表头样式单独写一个方法创建IFont加粗、ICellStyle设置背景色和边框。这部分代码每次写都很啰嗦拆资源时先找有没有封装好的 ExcelUtil没有就自己整理一个。sheet.SetColumnWidth(i, 20 * 256)第二参数单位是 1/256 字符宽度20 个字符宽度就是 20 乘 256这是 NPOI 里最容易忽略的单位换算。日期单元格要显式指定日期格式否则 Excel 打开后看到的可能是一串数字。如果导出要带筛选条件不要在 Handler 里重写一遍查询 SQL我一般让查询页面和导出共用同一个数据查询方法只是参数多一个 isExport这样导出数据和页面列表永远是对应同一份数据源。4.3 前端触发下载解决中文文件名与数据量边界前端导出不需要用 Ajax 拿文件流再转 Blob最简单的做法是直接构造一个location.href或临时表单提交请求。后端在输出文件时设置Content-Disposition浏览器会自动开始下载。这里最容易踩坑的是中文文件名HTTP 头不支持非 ASCII 字符所以文件名要先做 URL 编码。public void WriteExcelToResponse(IWorkbook workbook, string fileName) { string encodedFileName HttpUtility.UrlEncode(fileName); context.Response.Clear(); context.Response.ContentType application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; context.Response.AddHeader(Content-Disposition, string.Format(attachment; filename{0}, encodedFileName)); workbook.Write(context.Response.OutputStream); context.Response.End(); }浏览器收到Content-Disposition: attachment后会自动下载下载后的文件名多数浏览器能识别编码过的中文。老版本 IE 对 UrlEncode 处理不统一如果还要兼容 IE文件名就改用拼音或英文我一般直接用中文名做 UrlEncode现代浏览器没有问题。数据量超过 5 万行时不建议导出一个 sheet可以分拆多个 sheet每个 1 万行。单次 HTTP 响应时间过长浏览器会超时大导出建议改成后台任务异步生成生成完成后再提示下载前端不要傻等一个几十秒的请求。5. EasyUI上传文件接入流程与避坑清单5.1 filebox接入前端组件的两个作用域EasyUI 的上传入口不是独立插件而是增强版的 file input叫 filebox。它本质上还是input typefile只是外面包了一层按钮样式和文字显示所以它的行为受浏览器文件上传机制约束。前端接法有两种一种是放在普通 form 里一起提交另一种是监听onChange后用 FormData 单独异步上传。拆这套资源时我注意到很多例子里都在用第二种却没人讲为什么——因为 filebox 放在 form 里配合其它文本字段一起提交时EasyUI 的 form 组件并不保证能正确编码 multipart 数据尤其用了form(submit)这种便捷方法时文件字段经常丢失。function uploadFile() { var file $(#upload-file).filebox(getValue); if (!file) { $.messager.alert(提示, 请先选择文件); return; } var formData new FormData(); formData.append(attachment, $(#upload-file).filebox(files)[0]); $.ajax({ url: /Handlers/UploadHandler.ashx?actionupload, type: POST, data: formData, processData: false, contentType: false, success: function (resp) { if (resp.code 0) { $(#attachment).val(resp.data); $.messager.show({ title: 提示, msg: 上传成功 }); } else { $.messager.alert(提示, resp.msg); } } }); }要点在processData: false和contentType: false缺一不可。前者让 jQuery 不尝试把 FormData 转成字符串后者让 jQuery 不强行设置 application/x-www-form-urlencoded浏览器会自己带上 multipart 边界符。filebox(files)是 EasyUI 暴露的原生文件数组取[0]就是第一个文件HTML 上写了multiplemultiple时就要循环 append 每一个文件后端也要对应遍历处理。5.2 服务端接收Request.Files遍历与存储策略C# 端接收文件很简单HttpRequest.Files集合里就是所有上传的文件。但存储策略要想清楚直接存原名问题很多中文文件名、包含路径符号的文件名、重名覆盖都会在后续使用中造成隐患。我的方案是统一用 GUID 重命名扩展名从原文件名里取保存目录按日期分一天一个文件夹。public void Upload(HttpContext context) { if (context.Request.Files.Count 0) { WriteJson(context, false, 没有接收到文件); return; } HttpPostedFile file context.Request.Files[0]; string ext Path.GetExtension(file.FileName).ToLower(); string[] allowedExts { .jpg, .png, .gif, .pdf, .docx, .xlsx }; if (!allowedExts.Contains(ext)) { WriteJson(context, false, 不允许的文件类型); return; } string dateFolder DateTime.Now.ToString(yyyyMMdd); string saveDir context.Server.MapPath(~/Upload/ dateFolder); if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir); string newName Guid.NewGuid().ToString(N) ext; file.SaveAs(Path.Combine(saveDir, newName)); WriteJson(context, true, 上传成功, /Upload/ dateFolder / newName); }Path.GetExtension的结果带点所以allowedExts里必须写成.jpg而不是jpg。context.Server.MapPath返回物理路径SaveAs只能用物理路径而返回给前端的下载路径是虚拟路径二者不要混用。Guid.NewGuid().ToString(N)去掉横杠生成 32 位十六进制字符串避免文件名里出现横杠带来的 URL 解析歧义。多文件并发上传时两个请求同一毫秒进来的可能性不是没有用时间戳重名会覆盖文件GUID 从根上绕开这个问题。保存目录按月还是按天建取决于文件量和备份策略按天分目录后某一天的数据可以单独迁移冷备更方便。5.3 避坑清单三条上传翻车记录和对应的修复办法上传功能代码量不大却是最容易让新人在生产环境翻车的模块。我把拆这套资源过程中遇到的问题整理成三条避坑记录按“现象 - 原因 - 解决”的套路写方便你照着排查。第一条现象是文件超过几 MB 后一点上传就报错或者 IIS 直接返回 404 页面。原因是 ASP.NET 在 .NET Framework 4.0 以后有两层上传大小限制一层是httpRuntime maxRequestLength默认 4096 KB另一层是 IIS 的maxAllowedContentLength默认 30000000 字节但只在 30MB 附近生效。你只改一层不够。解决方法是同时改 system.web 里的 maxRequestLength 和 system.webServer/security 里的 maxAllowedContentLength并设为同一个值我一般设 20MB。注意前者单位是 KB后者单位是字节别把数值写反。system.web httpRuntime maxRequestLength20480 executionTimeout120 / /system.web system.webServer security requestFiltering requestLimits maxAllowedContentLength20971520 / /requestFiltering /security /system.webServer第二条现象是上传成功后数据库里存了文件名但前端访问时返回 404。原因是保存数据库的是物理路径而物理路径根本不在 Web 站点虚拟目录里浏览器无法访问。解决方法是数据库只存虚拟路径就是Server.MapPath之前的 URL 相对路径像上面代码里的/Upload/20250101/xxxx.jpg。如果老数据里已经存了物理路径后端要写一个文件响应 Handler 按相对路径读取并输出文件流但这是绕路方案新代码直接存虚拟路径别走回头路。第三条现象是 EasyUI 的 filebox 在 form(submit) 时其它字段都到了后台唯独文件字段为空。原因是 filebox 虽然长得像按钮但它不是 textboxform 组件序列化表单时不会自动带上 file 控件的二进制内容。解决方法是回到 5.1 节的 FormData 异步提交方式或者用原生 form 的enctypemultipart/form-data配合 submit 按钮提交不要走 EasyUI 的 form submit 简写。6. 联调时的一个验证习惯从F12到接口日志资源和代码都跑到本地后真正折磨人的往往是联调阶段。前端把按钮一页一页铺开后端把接口一个接一个写完两边各自觉得约定好了一合起来就翻车。我养成的第一个习惯是打开浏览器 F12 开发者工具把 Network 面板保持在台上。点删除之前先看一眼发出的 POST 请求体Content-Type 是不是 application/x-www-form-urlencoded参数里有没有多余的空格返回的 JSON 是不是和 EasyUI 期望的字段名一致。大多数增删改的问题在 F12 里半分钟就能定位根本不用加日志。第二个习惯是给后端每个 Handler 的入口加一行请求日志。真出问题时用户说“我这边点保存没反应”你反问一句“把 F12 的报错截图给我”不现实反而是服务端日志能还原当时的数据。我之前在一个老系统上加过滚动文件日志每天一个文本文件里面记录请求参数、响应内容和执行耗时格式很简单日期、action、参数字典、耗时。这个习惯救过我一次某次批量删除超时用户反馈接口无响应日志里记录到 ids 参数有 8000 多个数据库执行 DELETE 走了全表扫描问题一目了然。第三个习惯是维护一份前后端字段对照表。EasyUI 页面上的 field、C# 代码里读的 Form 参数、数据库表的列名三方经常不一致。我之前就栽过一次跟头前端UserName后端读Username数据库列叫name三处两种写法查数据查了半天最后发现大小写不敏感时能跑一换部署环境就报错。从那以后我每次接手这类 C#EasyUI 的资源第一步先建一个简单的表格把三层命名列出来逐字段核对确认一次才动代码核对时顺手把表单的 name、文本框的 id、后端读取的参数名统一成同一种风格。这习惯坚持下来项目里的低级错误少了一大半。希望帮到你。本文还有配套的精品资源点击获取
返回列表