简介面向ASP.NET MVC初学者的完整CRUD示例工程演示基于Layui前端框架实现用户管理中的增删改查操作。资源以Visual Studio解决方案为主包含Model、View、Controller三层代码以及Entity Framework相关配置、SQLite数据库文件与AJAX交互脚本可帮助理解从页面交互到后端处理的完整链路。压缩包共687个文件大小65.44MB核心文件包括16个cs源码、30个cshtml视图、54个js脚本及35个css样式另含运行时所需的dll与配置文件解压后可直接打开解决方案进行学习调试。目前已有494人学习适合刚接触MVC框架或希望快速搭建Layui后台界面的开发者参考。从数据表设计到列表展示、新增编辑删除弹窗再到分页排序搜索均可对照源码一一验证能够较快掌握数据验证、AJAX提交及异常处理等实用技巧。整体目录结构清晰可直接用于课程设计与项目实战练手。1. 先看效果一个能直接跑的 Asp.Net MVC Layui 增删改查如果你刚接触 Asp.Net MVC想找一个打开就能跑的前后端完整增删改查例子这个项目值得多看两眼。它的功能范围非常朴素列表展示、新增、编辑、删除界面用 Layui 渲染后端用 MVC Controller 接收请求并写回数据库整个请求链路从浏览器页面到数据库再返回能一次说清。我当初用这套组合接手第一个后台页面时就是把静态表格改成可维护的数据管理页。调通之后才明白MVC 的路由、模型绑定和 Layui 的表格请求格式才是这类项目真正的关键点。这个项目适合正在学 MVC 的开发者也适合想快速搭后台管理页面的朋友看完基本能把 Controller、View、JsonResult 这三个角色串起来。2. 技术选型与架构为什么用 Asp.Net MVC 配 Layui2.1 技术选型MVC 负责数据Layui 负责界面Asp.Net MVC 的定位非常清晰通过路由把 URL 映射到 Controller 的 ActionAction 里处理业务、操作数据库再返回 View 或 JsonResult。它天然适合做“页面 接口”混合型应用尤其是后台管理系统。对于增删改查这种需求MVC 的模型绑定能直接把表单字段映射成 C# 对象免去手写取值赋值的样板代码。Layui 则是一个纯前端 UI 框架它和 React、Vue 那些需要构建工具链的方案不同。用 Layui 不需要 npm、webpack只要把layui.css和layui.js放到项目的静态目录再在页面里引一下就能用。表格、弹窗、表单校验、分页组件都是现成的和 MVC 的 Razor 视图混用也不冲突。这两个东西凑在一起好处是各管各的Controller 只输出 JSON 或视图Layui 只负责把数据渲染成表格、把表单弹出来。你不用搞一套前后端分离的权限体系也不用担心跨域同一个站点内请求直接走相对路径调试起来非常直观。2.2 目录结构与请求链路一次完整的浏览器到数据库访问典型的解决方案里项目目录长这样/Controllers HomeController.cs /Models User.cs MyDbContext.cs /Views Home Index.cshtml Shared _Layout.cshtml /Content layui/css/layui.css layui/layui.js layui/lay/modules/table.js /Scripts App/Home/Index.js /Web.configControllers放路由处理逻辑Models放实体和数据库上下文Views放 Razor 视图Content和Scripts放静态资源。这个目录结构不是强制要求但按 MVC 默认约定放框架会自动找到对应的视图省去一堆路径配置。页面上的请求链路可以这样理解浏览器打开/Home/IndexMVC 路由定位到HomeController.Index()这个方法返回Index.cshtml视图。页面加载完Layui table 组件自动向/Home/GetList发一个 GET 请求Controller 查询数据库后返回 JSONLayui 拿到数据渲染表格。新增、编辑、删除则是弹窗里的表单通过 POST 请求提交到/Home/Create、/Home/Edit、/Home/Delete完成操作后再用table.reload刷新列表。实操上有个快速验证的小技巧新建项目后不要急着写代码先在HomeController里写一个返回 JSON 的 Action再弄一个空页面只放 Layui 表格把请求打通再一步一步加字段。这样能第一时间排查路由、静态文件和 JSON 格式的问题避免最后内容一多分不清是前端问题还是后端问题。3. 核心模块拆解一个请求从页面到数据库再回前端的过程3.1 实体模型与数据访问先有一张能操作的表不管前端多花哨增删改查的基础是实体类。这里的实体类可以和数据库表一一对应也可以直接用 EF 的 Code First 方式由模型生成数据库。下面是一个简单的用户实体// Models/User.cs public class User { // 主键自增 public int Id { get; set; } // 用户名必填 [Required] [StringLength(50)] public string Name { get; set; } // 年龄 public int Age { get; set; } // 邮箱 public string Email { get; set; } // 创建时间 public DateTime CreateTime { get; set; } // 是否启用0 停用1 启用 public int Status { get; set; } }配合一个简单的数据库上下文// Models/MyDbContext.cs using System.Data.Entity; public class MyDbContext : DbContext { // 连接字符串名称是 MyDbContext放在 Web.config 里 public MyDbContext() : base(nameMyDbContext) { } public DbSetUser Users { get; set; } }Web.config里对应的连接字符串大概是connectionStrings add nameMyDbContext providerNameSystem.Data.SqlClient connectionStringData Source.;Initial CatalogDemoDb;User IDsa;PasswordyourPassword;MultipleActiveResultSetstrue / /connectionStrings实体里的[Required]、[StringLength]是校验特性模型绑定阶段就会生效不用在 Controller 里手写判断。CreateTime默认值可以在实体构造函数里赋值也可以在 Action 里手动指定。注意如果数据库不存在建议先打开数据库初始器或手动建表否则第一次访问会报错这个放到后面避坑部分单独说。3.2 控制器中的 CRUD一个 Controller 搞定全部请求控制器是增删改查的中枢。这里我按“列表查询、新增、编辑、删除”四个场景各写一个 Action代码并不复杂但每个返回值都有讲究// Controllers/HomeController.cs using System.Linq; using System.Web.Mvc; public class HomeController : Controller { private MyDbContext db new MyDbContext(); // 页面入口返回视图 public ActionResult Index() { return View(); } // 列表数据接口给 Layui table 用 public JsonResult GetList(int page 1, int limit 10) { // 先拿 IQueryable方便后续加查询条件 var query db.Users.AsQueryable(); // 统计总数必须在分页之前执行 var total query.Count(); // 分页查询按 Id 倒序 var list query.OrderByDescending(u u.Id) .Skip((page - 1) * limit) .Take(limit) .Select(u new { u.Id, u.Name, u.Age, u.Email, u.Status, // 日期格式要提前转成字符串否则前端拿到的是乱码时间 CreateTime u.CreateTime.ToString(yyyy-MM-dd HH:mm:ss) }) .ToList(); // Layui table 期望的固定格式 var result new { code 0, msg , count total, data list }; return Json(result, JsonRequestBehavior.AllowGet); } // 新增POST 接收表单字段 [HttpPost] public JsonResult Create(User user) { if (!ModelState.IsValid) { return Json(new { code 1, msg 参数校验失败 }); } user.CreateTime DateTime.Now; db.Users.Add(user); db.SaveChanges(); return Json(new { code 0, msg 新增成功 }); } // 编辑根据实体主键更新 [HttpPost] public JsonResult Edit(User user) { if (user.Id 0 || !ModelState.IsValid) { return Json(new { code 1, msg 参数校验失败 }); } db.Entry(user).State System.Data.Entity.EntityState.Modified; db.SaveChanges(); return Json(new { code 0, msg 修改成功 }); } // 删除只接收 id [HttpPost] public JsonResult Delete(int id) { var user db.Users.Find(id); if (user null) { return Json(new { code 1, msg 数据不存在 }); } db.Users.Remove(user); db.SaveChanges(); return Json(new { code 0, msg 删除成功 }); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } }GetList返回的是JsonResult但 MVC 默认不允许 GET 请求返回 JSON所以必须加上JsonRequestBehavior.AllowGet。很多人第一次写接口时在这里报错其实原因就是这个。Create和Edit的参数是User对象模型绑定会把请求里的表单字段自动映射到对象的同名属性。这里有个隐患Layui 默认用contentType: application/json发送请求体而 MVC 5 的默认模型绑定读的是表单格式两者对不上就会拿到null。最稳的做法是让 Layui 用表单序列化提交或者在 Action 参数上标记[FromBody]。这个坑我会在下一章展开。Delete只接收一个int id不接收整个实体目的就是防止前端伪造多余字段。edit 和 delete 尽量都用 POST 请求一方面是 MVC 的[HttpPost]限制另一方面也是避免地址栏误触。3.3 前端视图与 Layui 交互表格渲染和弹窗提交视图部分主要做两件事渲染表格、弹窗表单。Index.cshtml里先引入 Layui 的样式和脚本然后放一个表格容器和弹窗模板!-- Views/Home/Index.cshtml -- link href~/Content/layui/css/layui.css relstylesheet / div stylepadding: 20px; button idbtnAdd classlayui-btn新增用户/button table iduserTable lay-filteruserTable/table /div !-- 新增/编辑弹窗的表单模板 -- script typetext/html ideditFormTpl form ideditForm classlayui-form stylepadding: 20px; input typehidden nameId / div classlayui-form-item label classlayui-form-label姓名/label div classlayui-input-block input typetext nameName classlayui-input lay-verifyrequired / /div /div div classlayui-form-item label classlayui-form-label年龄/label div classlayui-input-block input typetext nameAge classlayui-input / /div /div div classlayui-form-item label classlayui-form-label邮箱/label div classlayui-input-block input typetext nameEmail classlayui-input / /div /div button typesubmit classlayui-btn layui-btn-primary styledisplay:none;提交/button /form /script对应的 JavaScript 用layui.use加载模块。表格配置里url指向GetListcols定义列toolbar显示操作按钮然后监听行工具事件和表单提交事件// Scripts/App/Home/Index.js layui.use([table, form, layer], function () { var table layui.table; var form layui.form; var layer layui.layer; // 渲染数据表格 table.render({ elem: #userTable, url: /Home/GetList, // 请求地址 page: true, // 开启分页 cols: [[ { field: Id, title: ID, width: 80 }, { field: Name, title: 姓名, width: 150 }, { field: Age, title: 年龄, width: 100 }, { field: Email, title: 邮箱, minWidth: 200 }, { field: CreateTime, title: 创建时间, width: 180 }, { title: 操作, toolbar: #toolbar, width: 180 } ]] }); // 行工具事件编辑、删除 table.on(tool(userTable), function (obj) { var data obj.data; if (obj.event edit) { openEditForm(data); } else if (obj.event delete) { layer.confirm(确定删除这条数据吗, function (index) { $.post(/Home/Delete, { id: data.Id }, function (res) { if (res.code 0) { layer.msg(res.msg); table.reload(userTable); // 刷新表格 } else { layer.msg(res.msg, { icon: 2 }); } }); layer.close(index); }); } }); // 新增按钮 $(#btnAdd).on(click, function () { openEditForm(null); }); function openEditForm(data) { var title data ? 编辑用户 : 新增用户; layer.open({ type: 1, title: title, area: [500px, 400px], content: $(#editFormTpl).html(), success: function () { if (data) { // 回填数据 form.val(editForm, { Id: data.Id, Name: data.Name, Age: data.Age, Email: data.Email }); } }, btn: [保存], yes: function (index, layero) { // 触发表单提交交给下面监听事件处理 $(button[typesubmit], layero).click(); } }); } // 表单提交事件 form.on(submit(editForm), function (data) { var field data.field; var url field.Id ? /Home/Edit : /Home/Create; $.post(url, field, function (res) { if (res.code 0) { layer.msg(res.msg); table.reload(userTable); layer.closeAll(); } else { layer.msg(res.msg, { icon: 2 }); } }); return false; // 阻止默认提交 }); });这里最关键的是表单的name属性要和实体属性名一致。nameId、nameName会让提交的数据自然对应 C# 的User.Id、User.Name模型绑定才能生效。Layui 的form.val用来回填表单但要注意隐藏域Id也会被一起赋值否则编辑保存时就会变成新增。4. 避坑指南增删改查最常见的 5 个报错与排查方法4.1 请求与参数绑定类问题现象一点击保存后数据库没插入数据控制器里user参数是null。原因Layui 表单默认请求头是application/json而传统 ASP.NET MVC 5 的默认模型绑定依赖application/x-www-form-urlencoded。如果用了类似的封装直接发送 JSON 字符串服务端接不住。解决最简单的方法在发送数据前用$.param(field)转成表单格式例如$.post(url, $.param(field), function (res) { ... });如果一定要用 JSON就在 Action 参数前加[FromBody]但 MVC 5 对[FromBody]支持比较有限建议还是统一走表单格式。现象二请求返回 404但控制器方法明明存在。原因没有给 Action 加[HttpPost]但前端发了 POST 请求。MVC 的默认规则是只能匹配同名同请求方式的 Action如果只定义了public JsonResult Edit(User user)没有标注[HttpPost]框架会按 GET 去找匹配不上就 404。解决给所有写操作加[HttpPost]同时在前端确认发起的是 POST。用$.post一般不会出错但如果你用$.ajax又忘了写type就会踩中。现象三编辑保存时Id一直是 0导致新增了一条而不是修改。原因弹窗里隐藏域Id没有回填。form.val只能赋值给具有layui-form容器内的表单元素如果隐藏域在模板里已经有了但form.val时data.Id为undefined就会把Id清空。解决在success回调里先判断data是否为空再回填同时隐藏域建议用input typehidden nameId value-1 /编辑时改为真实 Id新增时保持 -1后端通过Id 0判断是新增。4.2 数据返回与展示类问题现象四接口返回的 JSON 正常但表格一片空白。原因Layui 的 table 组件对响应格式有固定要求必须是{code:0,msg:,count:100,data:[]}。直接返回ListUser或者new { data list }都被判为格式错误表格自然不渲染。解决在 Controller 中组装一个匿名对象包含code、msg、count、data四个字段。尤其是code必须是数字 0不能是字符串0。现象五日期字段显示成/Date(123456789)/这种奇怪格式。原因这是 ASP.NET 的 JavaScriptSerializer 序列化 DateTime 时的默认行为。Layui 拿到这种字符串不会自动转换只能显示原始值。解决在查询时就用.Select(u new { CreateTime u.CreateTime.ToString(yyyy-MM-dd HH:mm:ss) })转成字符串或者在实体上加一个未映射的格式化属性。最简单的还是前者一劳永逸。提示调试这类问题时优先看浏览器 Network 面板里请求的响应体再用 Layui 表格要求的最简格式去比对。这样就能快速判断是 Controller 返回格式不对还是前端字段匹配不上。5. 二次开发思路如何把简单 CRUD 扩展成可维护后台5.1 先定义统一返回格式再加查询和日志这个项目的核心代码很容易扩展但我不建议继续往 Controller 里堆逻辑。第一次改造我会先抽出两个基础部分统一返回对象和查询条件。先定一个简单的响应类public class ApiResult { public int code { get; set; } public string msg { get; set; } public long count { get; set; } public object data { get; set; } public static ApiResult Success(object data null, long count 0) { return new ApiResult { code 0, msg 成功, data data, count count }; } public static ApiResult Fail(string msg) { return new ApiResult { code 1, msg msg }; } }然后给GetList加一个搜索关键字参数比如按姓名模糊查询public JsonResult GetList(string keyword, int page 1, int limit 10) { var query db.Users.AsQueryable(); if (!string.IsNullOrEmpty(keyword)) { query query.Where(u u.Name.Contains(keyword)); } var total query.Count(); var list query.OrderByDescending(u u.Id) .Skip((page - 1) * limit) .Take(limit) .Select(u new { ... }) .ToList(); return Json(ApiResult.Success(list, total), JsonRequestBehavior.AllowGet); }前端只要在表格的where参数里传keyword即可table.reload(userTable, { where: { keyword: $(#keyword).val() } });再往后可以把DbContext操作抽到一个UserService层Controller 只负责接收参数和返回结果。这样项目从“能跑”变成“能维护”后续加权限、加操作日志、加字段都不用动页面骨架。从那以后我每次写增删改查都强制先定义好响应格式再写接口少踩很多坑。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?