
简介本资源是一套基于Asp.Net MVC框架与Layui前端UI实现的完整CRUD增删改查功能示例项目面向Web开发初学者及.NET全栈入门者聚焦MVC分层架构实践、前后端协同交互与基础业务功能落地。压缩包共687个文件涵盖250个运行依赖dll、54个Layui及业务逻辑js、35个cshtml视图页、33个config配置文件、35个css样式资源以及cs模型类、xml配置、sqlite轻量数据库等整体大小65.44MB结构清晰体现Model-View-Controller典型组织方式。已有494人学习下载适合用于快速搭建本地开发环境、理解EF数据操作流程、掌握Layui表格弹窗表单验证的集成用法并可直接复用AJAX请求封装、分页排序组件及基础安全处理逻辑。1. 一个能当天跑通、第二天就能塞进真实业务的后台管理骨架Asp.Net MVC Layui 的 CRUD 实战模板你有没有遇到过这种场景接了个内部系统需求要快速搭个带用户管理、商品列表、订单查询的后台老板说“下周初就要能填数据”但你翻遍 NuGet 和 GitHub不是太重带全套权限工作流审计日志就是太轻只有 HTML 静态页假数据。这个 Asp.Net MVC Layui 的增删改查模板就是为这种“夹缝时刻”生的——它不讲高大上的架构演进只做一件事用最直白的 Controller/View/Model 三层配一套开箱即用的 Layui 表单和表格把 Create、Read、Update、Delete 四个动作焊死在浏览器里。它适合刚从 Web Forms 转过来、对 MVC 生命周期还不熟的开发者也适合需要快速交付 MVP 的小团队。项目没用 Entity Framework Code First 自动生成迁移脚本那种“玄学流程”而是手写 SQL Server 数据访问层DAL连连接字符串都明晃晃写在web.config里Layui 版本锁定在 2.8.18避开了 3.x 的 breaking change所有 JS/CSS 都本地化不走 CDN。换句话说你下载解压改个数据库名F5 一按列表就出来了——没有“请先安装 Node.js”“请配置 webpack alias”“请运行 migration init”这类后悔药式前置条件。2. 项目结构与技术栈拆解为什么选 MVC 而不是 Core为什么 Layui 不换 Vue2.1 目录结构五层物理隔离拒绝“上帝 Controller”项目采用经典 ASP.NET MVC 5 的默认结构但做了三处关键裁剪删除了App_Start/BundleConfig.csLayui 所有资源本地化不走 Bundle合并了Models下的ViewModel和Entity新手阶段不引入 AutoMapper 层级避免黑匣子Controllers下仅保留ProductController.cs示例模块无 AccountController 或 Home 等干扰项完整目录树如下含关键文件说明/Controllers └── ProductController.cs ← 核心业务控制器含 Index/Create/Edit/Delete 五个 Action /Models └── Product.cs ← EF 实体类字段与数据库表完全映射Id, Name, Price, Status └── ProductContext.cs ← 继承 DbContext只注册 Product DbSet无其他模型 /DataAccess └── ProductDAL.cs ← 手写 ADO.NET 封装层含 GetList()/GetById()/Insert()/Update()/Delete() 五个方法 /Views └── Product/ ├── Index.cshtml ← Layui 表格 搜索栏 新增按钮含 layui.use([table,form]...) ├── Create.cshtml ← Layui 表单form.on(submit, ...) input[nameName] 等原生命名 ├── Edit.cshtml ← 同 Create但 form action 指向 /Product/Edit/{id} └── _Layout.cshtml ← 极简母版页只加载 layui.css layui.js 全局 JS 初始化 /Content └── layui/ ← 完整 Layui 2.8.18 发行包含 css/layui.css, js/layui.js, modules/table.js 等 /Scripts └── product-index.js ← 专用于 Index 页面的 JS初始化表格、绑定搜索、处理操作列事件提示DataAccess文件夹是本项目与“纯 MVC 教程”的分水岭。它不用 EF 的 SaveChanges()而是用SqlCommandSqlParameter手拼 SQL比如UPDATE Product SET NameName,PricePrice WHERE IdId。好处是执行计划透明、调试时能直接看到发出去的 SQL坏处是你得自己写参数防注入——但本项目所有 DAL 方法都已强制使用SqlParameter无需你补漏。2.2 技术选型逻辑MVC 5 是稳Layui 是快维度选择依据替代方案为何被弃用框架ASP.NET MVC 5.NET Framework 4.7.2ASP.NET Core MVC需额外装 SDK、配置 Kestrel、处理跨域对“今天部署明天上线”场景冗余Web FormsViewState 体积大Layui 表单绑定困难UI 框架Layui 2.8.18本地化全量包Bootstrap 5需额外引入 Popper.js表格组件弱于 Layui 的table.render()Vue 3需 setup script ref 响应式学习成本陡增数据访问ADO.NET 手写 DAL非 EF Code First非 DapperEntity Framework自动生成的 SQL 常含 N1 查询新手调优困难Dapper需手动写 SQL 字符串易出拼接漏洞部署方式IIS 传统站点部署web.config 中compilation targetFramework4.7.2 /Azure App Service需配置 .NET Framework 版本且免费层不支持远程调试Docker.NET Framework 容器镜像体积大启动慢关键决策点在于当你的目标是“让业务方能自己点按钮新增一条记录”而不是“展示你精通微服务治理”时少一层抽象就是少一个翻车点。比如ProductController.Index()方法里它不调用IProductService而是直接 newProductDAL()再调dal.GetList()——代码丑但断点打进去哪一行卡住一目了然。2.3 核心依赖清单NuGet 包仅 3 个无隐藏依赖项目packages.config明确列出以下依赖全部来自官方 NuGet 源无第三方私服package idjQuery version3.6.0 targetFrameworknet472 / package idMicrosoft.AspNet.Mvc version5.2.9 targetFrameworknet472 / package idMicrosoft.AspNet.WebPages version3.2.9 targetFrameworknet472 /注意Microsoft.AspNet.Razor未单独列出因它已包含在Microsoft.AspNet.Mvc5.2.9 中。零容忍项❌ 无Newtonsoft.JsonLayui 返回 JSON 用JsonResultMVC 内置JavaScriptSerializer足够❌ 无Unity或Autofac无 IOC 容器Controller 构造函数无参数❌ 无log4net错误日志直接写System.Diagnostics.Debug.WriteLine()生产环境替换为EventLog即可提示如果你在 VS2022 中打开此项目提示“需要安装 .NET Framework 4.7.2 Developer Pack”请勿慌——这是正常现象。去微软官网下载对应 installer 即可安装过程 3 分钟比配通一个 Webpack loader 快 10 倍。3. 运行前必改的 3 处配置数据库、端口、Layui 路径3.1 数据库连接字符串改web.config里的 connectionStrings 节点打开/web.config定位到connectionStrings节点将默认值add nameDefaultConnection connectionStringData Source.;Initial CatalogDemoDB;Integrated Securitytrue; providerNameSystem.Data.SqlClient /替换成你本地 SQL Server 实例的实际配置。常见修改场景SQL Server ExpressData SourceYOUR-PC\SQLEXPRESS;...用户名密码登录Data Source192.168.1.100;Initial CatalogDemoDB;User IDsa;Passwordyourpass;端口非 1433Data Source192.168.1.100,1434;...注意逗号非冒号注意Initial Catalog即数据库名必须存在。若不存在请先用 SSMS 创建空库DemoDB再运行下方建表 SQL。3.2 数据库建表脚本复制粘贴即可无外键无索引在 SQL Server Management Studio 中执行以下脚本共 1 张表5 个字段USE [DemoDB] GO CREATE TABLE [dbo].[Product]( [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, [Name] [nvarchar](100) NOT NULL, [Price] [decimal](18, 2) NOT NULL DEFAULT ((0)), [Status] [tinyint] NOT NULL DEFAULT ((1)), -- 1:上架, 0:下架 [CreateTime] [datetime] NOT NULL DEFAULT (getdate()) ) GO -- 插入测试数据可选 INSERT INTO [Product] ([Name],[Price],[Status]) VALUES (iPhone 15, 5999.00, 1), (MacBook Pro, 12999.00, 1)关键设计点Status字段用tinyint而非bit因为 Layui 表单中下拉框常需多状态如 0禁用,1启用,2审核中bit只能存 0/1CreateTime默认getdate()Controller 层不传该字段避免时区问题无外键约束因本模板暂不涉及关联查询如 Category 表后续扩展时再加。3.3 Layui 资源路径校验确认layui.js加载无 404打开/Views/Shared/_Layout.cshtml检查以下两行是否指向正确路径link relstylesheet href~/Content/layui/css/layui.css script src~/Content/layui/layui.js/script若你解压后把layui文件夹放到了其他位置如/Scripts/layui/则必须同步修改这两行。验证方法F12 打开浏览器开发者工具 → Network 标签页 → 刷新页面 → 查看layui.css和layui.js是否返回 200。若返回 404请立即修正路径——这是新手跑不通的最高频原因。3.4 启动项目IIS Express vs 本地 IIS选哪个开发阶段推荐VS 内置 IIS Express。右键项目 → “设为启动项目” → 按 CtrlF5不调试。URL 通常为http://localhost:50000/。演示阶段推荐发布到本地 IIS。右键项目 → “发布” → 选择“文件系统” → 目标位置设为C:\inetpub\wwwroot\productadmin→ 在 IIS 管理器中添加网站物理路径指向该文件夹绑定http:*:8080:。提示若用 IIS Express 启动时报错 “无法启动 IIS Express”请检查端口是否被占用如 Skype 占用 80 端口。解决方案右键项目 → “属性” → “Web” 选项卡 → 修改“项目 URL”端口号为50001或其他空闲端口。4. 核心代码逐行解析从 Controller 到 Layui 表格渲染的完整链路4.1 ProductController五个 Action 如何串联起 CRUD/Controllers/ProductController.cs是整个流程的中枢。我们以Index和Edit为例逐行拆解public class ProductController : Controller { // GET: Product public ActionResult Index() { var dal new ProductDAL(); // 1. 实例化数据访问层 var list dal.GetList(); // 2. 调用 DAL 获取全部产品返回 ListProduct return View(list); // 3. 将数据传给 Index.cshtml 视图 } // GET: Product/Edit/5 public ActionResult Edit(int id) { var dal new ProductDAL(); var model dal.GetById(id); // 4. 根据 ID 查询单条记录 if (model null) // 5. ID 不存在则跳转 404 { return HttpNotFound(); } return View(model); // 6. 将单条数据传给 Edit.cshtml } // POST: Product/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit([Bind(Include Id,Name,Price,Status)] Product product) { if (ModelState.IsValid) // 7. MVC 自动验证 [Required] 等特性 { var dal new ProductDAL(); dal.Update(product); // 8. 调用 DAL 更新数据库 return RedirectToAction(Index); // 9. 重定向回列表页防止重复提交 } return View(product); // 10. 验证失败则重新显示表单含错误提示 } }关键参数说明[Bind(Include Id,Name,Price,Status)]显式指定允许绑定的字段防止“ overposting 攻击”如恶意提交Status999关闭所有商品[ValidateAntiForgeryToken]配合Html.AntiForgeryToken()使用防御 CSRFreturn RedirectToAction(Index)这是 MVC 的黄金法则——POST 成功后必须重定向否则刷新页面会重复提交。4.2 ProductDAL手写 ADO.NET 的安全写法/DataAccess/ProductDAL.cs中Update方法是重点它展示了如何安全地执行参数化更新public void Update(Product model) { string sql UPDATE Product SET NameName,PricePrice,StatusStatus WHERE IdId; using (var conn new SqlConnection(ConfigurationManager.ConnectionStrings[DefaultConnection].ConnectionString)) { using (var cmd new SqlCommand(sql, conn)) { // 11. 为每个参数显式添加 SqlParameter类型与长度严格匹配 cmd.Parameters.Add(new SqlParameter(Name, SqlDbType.NVarChar, 100) { Value model.Name }); cmd.Parameters.Add(new SqlParameter(Price, SqlDbType.Decimal) { Value model.Price }); cmd.Parameters.Add(new SqlParameter(Status, SqlDbType.TinyInt) { Value model.Status }); cmd.Parameters.Add(new SqlParameter(Id, SqlDbType.Int) { Value model.Id }); conn.Open(); cmd.ExecuteNonQuery(); // 12. 执行非查询命令 } } }为什么不用cmd.Parameters.AddWithValue()AddWithValue会自动推断参数类型可能导致 SQL Server 执行计划缓存污染如第一次传abc推断为varchar(3)第二次传hello world就触发重编译本写法显式声明SqlDbType.NVarChar, 100与数据库字段nvarchar(100)完全一致性能更稳。4.3 Index.cshtmlLayui 表格如何接收 MVC 的 JSON 数据/Views/Product/Index.cshtml的核心是layui.table.render()它要求后端返回特定 JSON 结构table idproductTable lay-filterproductFilter/table script layui.use([table, form], function () { var table layui.table; table.render({ elem: #productTable, url: /Product/GetList, // 13. 请求地址返回 JSON 格式数据 page: true, cols: [[ { field: Id, title: ID, width: 80, sort: true }, { field: Name, title: 名称, edit: text }, // 14. 双击可编辑 { field: Price, title: 价格, templet: div¥{{d.Price}}/div }, { field: Status, title: 状态, templet: #statusTpl }, // 15. 使用模板渲染 { title: 操作, toolbar: #barDemo, width: 150 } ]] }); }); /script !-- 状态模板 -- script typetext/html idstatusTpl {{# if(d.Status 1){ }} span classlayui-badge layui-bg-green上架/span {{# } else { }} span classlayui-badge layui-bg-gray下架/span {{# } }} /script !-- 工具栏模板 -- script typetext/html idbarDemo a classlayui-btn layui-btn-xs lay-eventedit编辑/a a classlayui-btn layui-btn-danger layui-btn-xs lay-eventdel删除/a /script后端GetListAction 如何响应在ProductController中新增public JsonResult GetList(int page 1, int limit 10) { var dal new ProductDAL(); var list dal.GetList(); // 注意此处未分页真实项目需加 offset/limit // Layui 分页要求格式{code:0, msg:, count:总条数, data:[{},{},...]} return Json(new { code 0, msg , count list.Count, data list }, JsonRequestBehavior.AllowGet); }注意JsonRequestBehavior.AllowGet是关键否则 AJAX 请求会报 405 错误。Layui 的url参数本质是 GET 请求必须允许。5. 避坑指南那些让你调试到凌晨三点的典型问题5.1 现象点击“编辑”按钮无反应F12 看 Network 无请求发出原因Index.cshtml中layui.use([table,form])未正确加载form模块导致lay-eventedit绑定失败。解决确认script标签内layui.use的数组包含form且table.render()后必须调用table.on(tool(productFilter), function(obj){...})绑定事件。完整事件绑定代码如下table.on(tool(productFilter), function (obj) { var data obj.data; // 获得当前行数据 var layEvent obj.event; // 获得 lay-event 对应的值 if (layEvent edit) { location.href /Product/Edit/ data.Id; // 跳转到编辑页 } else if (layEvent del) { layer.confirm(确定删除, function (index) { $.get(/Product/Delete/ data.Id, function (res) { if (res.code 0) { obj.del(); // 删除对应行 layer.close(index); } }); }); } });5.2 现象新增商品时提示“Validation failed”但所有字段都填了原因Product.cs模型类中Price字段未加[Range]特性而数据库是decimal(18,2)MVC 默认认为0是无效值。解决在Product.cs中为Price添加验证public class Product { public int Id { get; set; } [Required(ErrorMessage 名称不能为空)] [StringLength(100, ErrorMessage 名称不能超过100字符)] public string Name { get; set; } [Required(ErrorMessage 价格不能为空)] [Range(0.01, 99999999.99, ErrorMessage 价格必须在0.01~99999999.99之间)] public decimal Price { get; set; } [Required(ErrorMessage 状态不能为空)] public byte Status { get; set; } // 注意tinyint 对应 C# 的 byte非 int }5.3 现象Layui 表格显示“数据接口异常”Console 报错Uncaught TypeError: Cannot read property length of undefined原因GetListAction 返回的 JSON 中data字段为null而非空数组[]。Layui 表格要求data必须是数组。解决修改GetList方法确保list为空时返回空数组public JsonResult GetList(int page 1, int limit 10) { var dal new ProductDAL(); var list dal.GetList(); // 强制转换为 ListProduct避免 null var dataList list ?? new ListProduct(); return Json(new { code 0, msg , count dataList.Count, data dataList }, JsonRequestBehavior.AllowGet); }5.4 现象修改商品后列表页价格显示仍为旧值刷新页面才更新原因Layui 表格启用了cache: true默认开启会缓存 AJAX 响应导致GetList返回旧数据。解决在table.render()配置中显式关闭缓存table.render({ elem: #productTable, url: /Product/GetList, cache: false, // 关键禁用缓存 page: true, // ... 其他配置 });5.5 现象部署到 IIS 后所有 Layui 图标如 ✖️显示为方块原因IIS 默认未启用woff2字体 MIME 类型Layui 的图标字体iconfont.woff2被拒载。解决在 IIS 管理器中选中网站 → “MIME 类型” → 右侧“添加” → 扩展名填.woff2MIME 类型填font/woff2→ 点击确定。重启网站即可。6. 二次开发实战如何在 30 分钟内接入新模块以“订单管理”为例6.1 新增 Order 模块的四步法假设你要增加“订单管理”功能复用本项目的结构只需四步步骤 1创建数据库表SQL ServerUSE [DemoDB] GO CREATE TABLE [dbo].[Order]( [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, [OrderNo] [varchar](20) NOT NULL, [ProductId] [int] NOT NULL, [Quantity] [int] NOT NULL DEFAULT ((1)), [TotalAmount] [decimal](18, 2) NOT NULL, [CreateTime] [datetime] NOT NULL DEFAULT (getdate()) ) GO步骤 2生成 Model 与 DAL手写非 EF/Models/Order.cs定义Id,OrderNo,ProductId,Quantity,TotalAmount,CreateTime属性/DataAccess/OrderDAL.cs复制ProductDAL.cs将所有Product替换为OrderSQL 语句改为SELECT * FROM [Order]等/Controllers/OrderController.cs复制ProductController.csAction 名称与路由保持一致Index,Create,Edit,Delete。步骤 3复用 Layui 模板最小改动/Views/Order/Index.cshtml复制/Views/Product/Index.cshtml仅修改table.render()的url为/Order/GetListcols字段改为OrderNo,ProductId等/Views/Order/Create.cshtml复制/Views/Product/Create.cshtml将input[nameName]改为input[nameOrderNo]并添加ProductId下拉框需在 Controller 中ViewBag.ProductList传入。步骤 4添加导航菜单修改_Layout.cshtml在nav classlayui-nav内追加li classlayui-nav-item a href/Order/Index订单管理/a /li提示不要试图用Html.ActionLink本项目所有导航都是纯 HTMLa标签杜绝 Razor 语法复杂度。6.2 关联查询实战在订单列表显示商品名称Order表有ProductId但列表需显示Product.Name。手写 DAL 中加入 JOIN 查询// /DataAccess/OrderDAL.cs public ListOrderWithProductName GetOrderListWithProduct() { string sql SELECT o.Id, o.OrderNo, p.Name AS ProductName, o.Quantity, o.TotalAmount, o.CreateTime FROM [Order] o INNER JOIN Product p ON o.ProductId p.Id; // ... 执行查询返回 ListOrderWithProductName }对应新建OrderWithProductName.cs模型非数据库实体public class OrderWithProductName { public int Id { get; set; } public string OrderNo { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } public decimal TotalAmount { get; set; } public DateTime CreateTime { get; set; } }OrderController.Index()中调用dal.GetOrderListWithProduct()视图中cols字段直接用ProductName。6.3 权限控制轻量接入基于 Session 的角色拦截若需区分“管理员”和“普通用户”在Global.asax.cs的Application_AuthenticateRequest中添加protected void Application_AuthenticateRequest(Object sender, EventArgs e) { if (Context.User ! null Context.User.Identity.IsAuthenticated) { // 假设登录后存 Session[Role] Admin 或 User var role Context.Session[Role] as string; if (!string.IsNullOrEmpty(role)) { var identity new GenericIdentity(Context.User.Identity.Name); var principal new GenericPrincipal(identity, new[] { role }); Context.User principal; } } }然后在OrderController上加特性[Authorize(Roles Admin)] // 仅管理员可访问 public class OrderController : Controller { ... }从那以后我每次接到“加个新模块”的需求都强制走一遍这四步法建表 → 写 DAL → 拷 Controller/View → 改导航。它不优雅但能保证我在咖啡凉透前把链接发给测试同学。希望帮到你。本文还有配套的精品资源点击获取