简介这是一份ASP.NET电子商务网站的完整示例项目主要面向Web开发初学者、计算机专业学生及需要课程设计参考的开发者覆盖商品展示、购物车管理、用户注册登录、订单处理等电商核心功能。资源以RAR压缩包形式提供共59个文件体积仅400KB左右。其中7个ASPX页面承载前台展示逻辑12个C#文件实现后台事件处理与数据访问4个ASCX用户控件用于公共头部、搜索等复用模块4个Access数据库文件MDB存储商品、用户及订单数据同时附带GIF/JPG图片与CSS样式方便直接还原页面效果。项目结构清晰适合在Visual Studio中结合IIS进行调试运行。通过学习这套代码可以掌握ASP.NET Web Forms的页面生命周期、服务器控件绑定、Session状态管理以及ADO.NET数据库操作等关键知识点也能了解到母版页和用户控件如何提升开发效率。目前已有143人学习浏览对希望快速参考完整购物流程实现方式的初学者来说是一份直观且容易上手的入门资料。1. 一个 ASP.NET 电商网站这套项目能干什么、适合谁我拿到这个 asp.net 电子商务网站的第一反应是不太信——界面是老式 Bootstrap 风格支付也是模拟的看起来不像有什么工程价值。真正在本地跑通以后我发现它的功能闭环非常完整商品分类筛选、购物车增删、下单事务、后台商品上下架和订单管理全是真实可用的代码数据库脚本和种子数据也都齐。对于想在一个可运行的电商网站基础上做二次开发的开发者来说这份资源比零散教程实在得多——毕设起步、接手旧系统前先复现一遍、或者想快速搭一个商城原型都能直接拿来用。2. 项目结构拆解解决方案分层与数据库表设计2.1 技术栈与整体分层这套电商网站的技术栈非常典型ASP.NET MVC 5 负责页面与路由Entity Framework 6 做 ORMSQL Server 存数据前端是 Bootstrap 加 jQuery 的经典组合。没有前后端分离也没有微服务就是一个标准的 .NET Framework 时代三层架构。这种老派写法在今天的视角下不算时髦但胜在结构直白适合用来理解“请求怎么进、数据怎么出”的完整链路。三个工程职责划分得很清楚WebUI 是启动项目装控制器和视图负责 HTTP 请求的分发和页面渲染Services 放业务逻辑购物车、订单、库存扣减都在这层DataAccess 是数据访问层EF 的 DbContext 和仓储类都在这里。不同层次不能越级调用——Controller 不会直接 new 一个 DbContext 去查数据库而是调 Service再由 Service 走仓储。我拿到代码后最先看的不是页面效果而是这个依赖方向因为它决定了以后改了业务逻辑要不要牵连到页面层。需要特别提醒的是 Models 文件夹里的类要分清两类一类是 EF 实体类字段和数据库表几乎一一对应比如 Product 有 Id、Name、Price另一类是 ViewModel是给视图用的字段可以少于实体也可以多出“确认密码”“验证码”这种不落库的东西。有些项目把它们混着放时间一长就说不清哪个对应哪张表了。我习惯把实体类放 DataAccess 的 Entities 子目录ViewModel 留在 WebUI/Models 里靠文件夹就能区分。2.2 解决方案目录每个文件夹的职责先看目录结构这份代码的工程划分是一个标准的 MVC 5 解决方案EShop解决方案根目录 ├── WebUI # 启动项目右键设为启动项目 │ ├── Controllers/ # 控制器处理请求 │ │ ├── HomeController.cs │ │ ├── ProductController.cs │ │ ├── CartController.cs │ │ ├── OrderController.cs │ │ └── Admin/ # 后台管理控制器 │ ├── Views/ # Razor 视图 │ │ ├── Home/ │ │ ├── Product/ │ │ ├── Cart/ │ │ ├── Order/ │ │ └── Shared/ # 布局页 _Layout.cshtml、错误页 │ ├── Models/ # ViewModel不是 EF 实体 │ ├── App_Data/ # SQL 脚本、本地数据库文件 │ └── Web.config # 连接字符串、认证、编译配置 │ ├── Services/ # 业务逻辑层 │ ├── CartService.cs │ ├── OrderService.cs │ └── ProductService.cs │ └── DataAccess/ # 数据访问层 ├── EShopDbContext.cs # EF 上下文持有 DbSet ├── Entities/ # EF 实体类 └── Repositories/ # 仓储类封装数据库操作有几个点要单独说明。WebUI 是整个解决方案的启动项目F5 调试前要在解决方案资源管理器里右键它选择“设为启动项目”否则一按 F5 可能跑的是别的工程。Controllers/Admin 是一个子文件夹里面是带[Authorize(Roles Admin)]的后台控制器和前台控制器分开权限从第一层就隔开。Services 层是我看代码时最关注的地方。OrderService 里的下单方法包含了事务和库存扣减CartService 管购物车的增删改ProductService 负责商品查询和上下架。Controller 只做参数接收和视图返回不做金额计算这类业务逻辑。这样设计的好处是以后把网站加上 Web API 接口或者换掉 MVC 整套壳子订单和购物车的逻辑还能复用。DataAccess 里的 EShopDbContext 继承 DbContextProducts、Orders、Users 的 DbSet 都在这里暴露给上层。2.3 核心数据库表商品、订单、用户怎么设计数据库脚本在 App_Data 里手工建库和执行脚本都是常见做法。商品表是最核心的-- 商品表电商网站的基础数据 CREATE TABLE Product ( Id INT IDENTITY(1,1) PRIMARY KEY, -- 自增主键 CategoryId INT NOT NULL, -- 分类外键指向 Category 表 Name NVARCHAR(100) NOT NULL, -- 商品名称 Price DECIMAL(18,2) NOT NULL, -- 售价用 decimal 不用 float Stock INT NOT NULL DEFAULT 0, -- 库存数量 ImageUrl NVARCHAR(255), -- 商品图片路径 Status TINYINT NOT NULL DEFAULT 1, -- 1上架 0下架 CreatedAt DATETIME NOT NULL DEFAULT GETDATE() -- 创建时间 );重点看三处。Price 用 DECIMAL(18,2) 是电商的硬性要求用 float 算金额会在小数位翻车Status 用 TINYINT 而不是直接 DELETE下架就是改状态保留商品历史数据CreatedAt 靠数据库默认值生成应用层不用手动赋值。CategoryId 是外键但脚本里不一定写了 FOREIGN KEY 约束看实际执行情况我一般会补上。订单主表和订单明细表是另一个关键设计-- 订单主表一次下单一条记录 CREATE TABLE [Order] ( Id INT IDENTITY(1,1) PRIMARY KEY, OrderNo NVARCHAR(32) NOT NULL, -- 业务订单号展示给用户 UserId INT NOT NULL, -- 下单用户 TotalAmount DECIMAL(18,2) NOT NULL, -- 总金额下单时算好 Status TINYINT NOT NULL DEFAULT 0, -- 0待支付 1已支付 2已发货 3已完成 4已取消 CreateTime DATETIME NOT NULL DEFAULT GETDATE() ); -- 订单明细表一个订单对应多条商品记录 CREATE TABLE OrderItem ( Id INT IDENTITY(1,1) PRIMARY KEY, OrderId INT NOT NULL, -- 关联订单主表 ProductId INT NOT NULL, -- 商品原 ID仅作追溯 ProductName NVARCHAR(100) NOT NULL, -- 商品名称快照 Price DECIMAL(18,2) NOT NULL, -- 下单时单价快照 Quantity INT NOT NULL -- 购买数量 );订单明细里特意冗余了 ProductName 和 Price。这是电商里的规矩下单后商品可能改名、改价甚至下架历史订单必须还能看到下单那一刻的商品信息。有一点容易被忽略——OrderItem 和 Product 之间不要再做外键级联否则商品被物理删除订单明细也会跟着出事实际项目里商品表大多只做软删除。用户表相对简单但密码字段是敏感点-- 用户表普通用户和管理员共用一张表 CREATE TABLE [User] ( Id INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(50) NOT NULL, PasswordHash NVARCHAR(128) NOT NULL, -- 存哈希别存明文 Role TINYINT NOT NULL DEFAULT 0, -- 0普通用户 1管理员 CreateTime DATETIME NOT NULL DEFAULT GETDATE() );密码字段是 PasswordHash 而不是 Password这是最基本的底线。登录校验时把用户输入的密码做同样的哈希再比对而不是把库里明文捞出来用等于号比较。项目里通常用的是 PBKDF2 或 BCrypt 一类的算法看代码里 PasswordHelper 工具类的实现就行。数据库初始化方式也要提一句如果是 EF Code First第一次运行会自动创建表如果是脚本方式需要先用 SSMS 手动执行 App_Data 下的建表脚本再改连接字符串。两种方式对应不同的开发习惯下面的运行指南会详细说。3. 核心模块拆解一个商品从浏览到下单的完整链路3.1 商品列表与筛选查询条件怎么拼商品列表页是整个网站的入口ProductController 的 Index 方法负责这件事public class ProductController : Controller { private readonly EShopDbContext _db; public ProductController() { _db new EShopDbContext(); // 每次请求一个实例请求结束释放 } // GET: /Product/Index?categoryId1keyword手机 public ActionResult Index(int? categoryId, string keyword) { // 只查上架商品下架的不在商城显示 var query _db.Products .Where(p p.Status 1); if (categoryId.HasValue) query query.Where(p p.CategoryId categoryId.Value); if (!string.IsNullOrEmpty(keyword)) query query.Where(p p.Name.Contains(keyword)); var list query .OrderByDescending(p p.CreatedAt) .ToList(); return View(list); } }三个参数值得关注。categoryId 是可空整数用户点分类链接时带着值进来没带就是 null说明用户在看全部分类keyword 是搜索框提交的文本用 Contains 生成 LIKE 查询EF 会转成参数化 SQL不用担心字符串拼接注入。整段代码的关键是 IQueryable 的延迟执行——前面对 query 的 Where 只是把表达式记下来到 ToList() 才真正向数据库发起查询所以分步判断条件再拼 SQL 不会影响性能。有一个细节容易踩坑构造函数里直接 new 了 DbContext但这个 Controller 没有重写 Dispose 去释放它。实际项目里我一般写一个 BaseController在 Dispose 里统一释放_db或者把上下文用 using 包起来。短期跑没问题长期跑会积累连接泄漏这是 EF 老项目的通病。3.2 购物车机制Session 方案与刷新丢失问题购物车是这个项目里最值得读的部分。它没建购物车表而是用 Session 存一个 List public class CartService { private const string CartKey CartItems; // 从 Session 读取购物车为空时初始化一个新列表 public ListCartItem GetItems(HttpContextBase context) { var items context.Session[CartKey] as ListCartItem; if (items null) { items new ListCartItem(); context.Session[CartKey] items; } return items; } // 加入购物车已存在的商品只累加数量 public void Add(HttpContextBase context, int productId, int quantity) { var items GetItems(context); var existing items.FirstOrDefault(i i.ProductId productId); if (existing ! null) existing.Quantity quantity; else items.Add(new CartItem { ProductId productId, Quantity quantity }); } }Session 方案的取舍很明确优点是不用建表代码量小适合中小型站点缺点是用户清缓存、登录状态过期、Session 重启购物车就丢了。如果你接手后想把购物车改成登录后持久保存常见做法是把 CartItem 落库UserId 关联用户Key 从 Session 换成 UserId逻辑上只是把 context.Session[CartKey] 换成数据库查询。CartItem 这个类推荐做成“商品快照 数量”的结构ProductId、ProductName、Price、Quantity 四个字段。购物车页面要直接显示商品名和单价老是从数据库逐条查商品表列表一多性能就吃亏。CartService 里 Add 方法已经处理了重复加购——同一商品第二次添加走累加分支不会在列表里出现两行相同商品。3.3 下单与库存扣减事务保证一致性OrderService.CreateOrder 是整份代码中最值得抄的一段public Order CreateOrder(int userId, ListCartItem cartItems) { using (var tx _db.Database.BeginTransaction()) { try { // 1. 创建订单主表 var order new Order { OrderNo GenerateOrderNo(), UserId userId, TotalAmount 0, Status 0 }; _db.Orders.Add(order); // 2. 逐条扣库存、算总价、生成明细 decimal total 0; foreach (var item in cartItems) { var product _db.Products .Where(p p.Id item.ProductId) .FirstOrDefault(); // 库存不足直接抛出整个事务回滚 if (product null || product.Stock item.Quantity) throw new Exception(商品库存不足); product.Stock - item.Quantity; // 明细里存商品快照防止后续改名改价影响历史订单 var orderItem new OrderItem { OrderId order.Id, ProductId product.Id, ProductName product.Name, Price product.Price, Quantity item.Quantity }; _db.OrderItems.Add(orderItem); total product.Price * item.Quantity; } order.TotalAmount total; _db.SaveChanges(); // 统一提交 tx.Commit(); return order; } catch { tx.Rollback(); // 任何一个环节失败全部回滚 throw; } } }这段的骨架是 BeginTransaction 包住整个流程创建订单、扣库存、写明细、算总价所有写操作在 SaveChanges 后统一 Commit任何一步抛异常就 Rollback。注意_db.Products.Where(...).FirstOrDefault()查出来的 product 是 EF 跟踪对象直接改它的 Stock 属性最终会体现在 SaveChanges 里不用显式调 Update。有一个并发隐患值得提醒两个请求同时读到 Stock1都通过判断然后各自扣减最终库存会变成 -1。常见防御法是扣减 SQL 带上条件例如_db.Database.ExecuteSqlCommand(UPDATE Product SET Stock Stock - {0} WHERE Id {1} AND Stock {0}, ...)或者用行锁。理解这个问题的存在比抄代码更重要。3.4 后台管理权限过滤与防伪造请求后台的控制器放在 Controllers/Admin 子目录用特性做权限控制[Authorize(Roles Admin)] public class AdminProductController : Controller { // GET: /Admin/Product/Edit/5 public ActionResult Edit(int id) { var product _db.Products.Find(id); if (product null) return HttpNotFound(); return View(product); } // POST: /Admin/Product/Edit [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(Product model) { if (ModelState.IsValid) { _db.Entry(model).State EntityState.Modified; _db.SaveChanges(); return RedirectToAction(Index); } return View(model); } }[Authorize(Roles Admin)]放在类上整个后台的入口就锁死了非管理员请求在进入方法前就被 MVC 过滤掉。视图里的编辑表单必须配合Html.AntiForgeryToken()和同名隐藏字段否则[ValidateAntiForgeryToken]会直接报错——这是防跨站请求伪造的标准姿势去掉了这个验证反而说明项目不安全。后台权限验证不只是页面隐藏按钮而是在每个请求上都生效。二次开发时如果要加“运营人员只能改商品、不能改订单”这类细粒度控制可以拆角色或者在方法上加自定义授权特性而不是只改前端界面。4. 本地运行指南从环境准备到页面跑通4.1 环境准备Visual Studio、SQL Server 与 NuGet 还原跑这套项目需要准备三样东西Visual Studio 2019 或 2022注意要勾选“.NET Framework 4.7 开发工具”组件否则项目文件加载时会报目标框架不受支持SQL Server 2016 及以上版本Express 版就够用但要保证 SQL Server 服务已启动最后是 NuGet 包还原项目里用了 Entity Framework 6打开解决方案后右键解决方案 → 还原 NuGet 包或者直接 F5 让 VS 自动还原。这里有一个常见的翻车点打开解决方案后一堆引用标着黄色感叹号。不要急着去网上找 DLL绝大多数情况是 NuGet 包没还原或者还原时网络断了。回到 NuGet 包管理器看“已安装”列表如果 EF、jQuery 这些是空的就重新还原一次。安装包时注意版本Entity Framework 6.2.0 以上配 MVC 5 是稳定组合不要手滑装成 EF Core。4.2 修改连接字符串Web.config 的两种配法连接字符串在 WebUI 的 Web.config 里默认是占位符需要改成你本地 SQL Server 的实例。SQL Server 身份验证账号密码方式是connectionStrings add nameEShopDbContext connectionStringServerlocalhost\SQLEXPRESS;DatabaseEShopDB;User Idsa;Password你的密码;TrustServerCertificateTrue; providerNameSystem.Data.SqlClient / /connectionStrings用 Windows 身份验证集成安全的方式是add nameEShopDbContext connectionStringServerlocalhost\SQLEXPRESS;DatabaseEShopDB;Integrated SecurityTrue; providerNameSystem.Data.SqlClient /参数解释Server 写 SQL Server 实例名默认实例写 localhost 或.命名实例写 localhost\实例名Database 是数据库名第一次跑时如果不存在EF Code First 会自动创建脚本方式则需要手动建库User Id/Password 是 SQL Server 登录账号Integrated SecurityTrue 代表用 Windows 当前账号登录踩坑少一些。TrustServerCertificateTrue 是连接本地 Express 实例时避免证书校验报错的常见补丁。最后确认 providerName 是 System.Data.SqlClient这是 .NET Framework 下连接 SQL Server 的标准提供程序改成别的会导致 EF 找不到数据库。4.3 建库、种子数据与首次启动数据库就绪后看 App_Data 里有没有 SQL 脚本。有脚本就用 SSMS 执行没有就靠 EF Code First把连接字符串的 Database 改成新库名启动项目设为 WebUI按 F5。EF 的 Initializer 如果配置了 CreateDatabaseIfNotExists 或 MigrateDatabaseToLatestVersion会在首次请求时自动建表种子数据也会通过 Seed 方法写入。如果想确认数据库有没有起来打开 SSMS 连本地实例看 EShopDB 下有没有 Product、[Order]、User 这几张表。完整启动顺序整理如下第一在解决方案资源管理器里右键 WebUI设为启动项目第二确认 Web.config 连接字符串指向可用实例第三按 F5 启动首次请求自动建库第四用种子账号登录去 /Admin/Product 验证管理员权限。种子数据里通常有一批测试商品和两个账号一个普通用户、一个管理员登录后直接去后台地址验证权限是否正常。启动时如果页面直接报 HTTP 500先切到调试器看异常类型。最常见的两种数据库连接异常说明 Web.config 的实例名或账号密码不对实体模型变更异常说明代码里的实体类和数据库表对不上EF 默认不会自动改表结构。前者去改连接字符串后者看第 5 章的排查顺序。4.4 换端口与 IIS Express 参数IIS Express 的端口在项目属性 → Web 里配置默认是随机端口比如 https://localhost:44300 或 http://localhost:53800。如果改了端口记得同步检查 Views/Shared 布局里有没有写死的绝对链接否则页面跳转会 404。项目若配置了 HTTPS浏览器首次访问会弹证书警告IIS Express 开发证书点“继续访问”即可。这些细节不影响功能但处理不好会在演示时显得项目是坏的。5. 避坑手册跑不起来时按这个顺序查5.1 数据库连接失败SqlException 的三种诱因现象启动后页面报 SqlException错误信息要么是“无法打开登录所请求的数据库”要么是“用户 sa 登录失败”。原因基本逃不出三个SQL Server 服务没启动连接字符串的 Server 实例名写错账号密码或身份验证模式不对。如果是“无法打开数据库”多半是 Database 名字和实际库名不一致或者库还没建。解决先用 SSMS 打开本地实例能连上说明服务正常再确认 Web.config 里 Server 和实际实例名一致默认实例就写 localhost别画蛇添足写 localhost\SQLEXPRESS除非你装的确实是命名实例。检查无误后重新编译启动。5.2 编译报错 CS0246类型或命名空间找不到现象一编译报一堆“未能找到类型或命名空间”的错误集中在 Entity Framework 相关的 using 上。原因NuGet 包没有还原成功或者项目引用的程序集版本和本地不一致。解决右键解决方案 → 还原 NuGet 包如果还不行删掉解决方案目录下的 packages 文件夹重新还原。检查项目目标框架是否统一——EF 6 在 .NET Framework 4.7 下正常但整个解决方案各工程的目标框架必须一致不能一个 4.7 一个 4.8 混着来。5.3 能登录但马上跳回登录页现象输入账号密码后提示登录成功紧接着又跳回登录页仿佛没登录过。原因第一反应是 Controller 没写 [Authorize]但更常见的是 Cookie 认证配置缺失MVC 的 AuthorizeAttribute 依赖 Forms Authentication 的 Cookie 写入另一个隐藏原因是种子账号的密码哈希比对到了错误字段登录其实失败了只是页面没提示。解决先打开 Web.config 检查 system.web 节点下 authentication mode 是不是 Forms再在代码里断点看登录方法返回的 bool 是 true 还是 false。true 还跳回登录页就是 Cookie 或路由的问题false 就是密码校验逻辑的问题。5.4 IIS Express 端口被占用现象F5 启动时 VS 报“端口已被占用”或者浏览器一直转圈页面半天不出来。原因之前调试的 iisexpress 进程异常退出后占着端口没释放这个属于 VS 时代的老玄学。解决任务管理器结束所有 iisexpress.exe 进程再重新启动或者右键项目 → 属性 → Web → 修改“项目 URL”的端口换一个不冲突的。5.5 页面中文乱码现象前台列表页、后台管理页的中文全是问号或乱码。原因数据库排序规则不是 Chinese_PRC或者文件保存编码不是 UTF-8常见于代码复制黏贴时把文件编码弄坏了。解决建库时显式指定排序规则例如 COLLATE Chinese_PRC_CI_AS已有库可以改库级排序规则。代码文件在 VS 里用“文件 → 高级保存选项”选 UTF-8 with BOM 重新保存。5.6 视图找不到打开 404 或 CS0103现象点击某个分类链接跳 404或者页面报 CS0103名称不存在。原因404 通常是路由或视图文件名拼写不一致Razor 视图默认按 Controller/Action 找 Views 下的同名文件CS0103 是视图里用了模型中不存在的属性比如在只传 List 的页面里调用 Model.SomeProperty。解决先看浏览器地址栏的 URL 对应哪个控制器方法再去 Views 目录按 Controller 名/方法名找 .cshtml 文件名字不一致就改视图文件名或改路由。属性报错就看视图顶部model声明的类型和 Controller 传进去的类型必须一致。6. 二次开发实战从改商品模块到验证完整订单流如果要动手改我建议从商品模块入手不要一上来就碰订单。给 Product 加一个“条码”字段改动会依次经过实体类、数据库表、后台编辑页、前台展示页走完一遍就理解整个数据流了。这里有一个血泪教训只改实体类不改数据库表EF 启动时模型校验会直接抛“模型与数据库不一致”的异常别指望运行时再处理。我一般先写 SQL 脚本给表加列再同步实体类顺序反了就会多花半小时排查。订单模块的改造重点是打通测试链路。项目里的支付是模拟的我通常直接在 OrderController 里加一个模拟支付方法把订单状态从待支付改成已支付// GET: /Order/PayMock/1001 public ActionResult PayMock(int orderId) { var order _db.Orders.Find(orderId); if (order ! null) { order.Status 1; // 待支付 - 已支付 _db.SaveChanges(); } return RedirectToAction(Detail, new { id orderId }); }加完这个方法不需要接真实支付网关就能完整验证“下单 → 支付 → 后台看单”的整条链路。最后每一步改动都要做端到端验证用普通用户账号登录 → 把商品加入购物车 → 提交订单 → 模拟支付 → 用管理员账号去后台看订单状态和金额。从那以后我每次改完一个模块都强制走一遍这条流程再顺手看一眼 SQL Server 里对应的表数据是否一致确认无误才算完成。这套流程帮我挡掉了很多“页面看着正常、库里数据是错的”的隐蔽问题希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?