恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

.NET Core MVC电商实战:依赖注入、购物车与订单模块实现

  • 首页
  • 资讯中心
  • /
  • .NET Core MVC电商实战:依赖注入、购物车与订单模块实现

相关资讯

GitLab与Jenkins CI/CD实战:从零搭建自动化部署流水线 2026/8/26 6:51:17
携程算法岗笔试解析:动态规划与图神经网络实战 2026/8/26 6:46:17
Kubernetes 命令行工具 kubectl 从入门到精通:安装、配置与实战指南 2026/8/26 6:46:17

最新资讯

数学建模竞赛实战:钢板切割路径优化算法与代码实现
C++ std::set自定义排序:从仿函数到严格弱序的完全指南
受电弓检测数据集实战:VOC格式解析与YOLO训练全流程
链表翻转与字符串相乘:算法面试高频题解析
Java调用金蝶云星空OSF接口:实现请假单自动同步的实战指南
C语言const修饰指针详解:四种形态、底层原理与工程实践

今日推荐

Python random 模块常用函数详解:从入门到实战
Hermes接入团队协作后,我推翻了三个效率假设
免费AI大模型调教指南:打造专属网文写作助手

本周热门

Nextcloud 桌面客户端:把同步交给它,你只管改文件
如何将 HTML 转成 Word 文档且格式不丢失?html-to-docx 使用教程
Anki 批量操作卡片完整指南:一次搞定上千张,不再逐张修改

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

.NET Core MVC电商实战:依赖注入、购物车与订单模块实现

发布时间:2026/8/26 6:51:17
.NET Core MVC电商实战:依赖注入、购物车与订单模块实现 在电商项目开发中我反复在依赖注入、模型绑定和购物车状态维护这几个环节卡住。网上资料不算少但大多是单个知识点的讲解缺少一个把 MVC 完整串起来的实战视角。本文结合 Udemy 课程《.NET Core MVC - The Complete Guide》中 E-commerce 项目的第三部分内容围绕电商场景整理一套 .NET Core MVC 实战方案。新手可以顺着文章把项目搭起来有经验的开发者可以直接跳到购物车模块和依赖注入原理部分查漏补缺。本文会讲清楚 MVC 架构的核心概念、ASP.NET Core 内置的依赖注入容器如何工作、购物车与订单模块怎么设计以及 SQL 注入、CSRF 等常见安全问题的防护思路。所有代码都围绕一个最小可运行的电商项目展开你可以直接复制、调整、运行再结合自己的业务继续扩展。1. 课程背景与 .NET Core MVC 基础1.1 为什么 .NET Core MVC 适合做电商项目很多人在选型时会把 .NET Core MVC 与传统的 Web Forms、或者前端的 Vue/React 方案放在一起比较。实际上.NET Core MVC 是 ASP.NET Core 中负责处理 Web 界面的一种开发模式它既支持传统的服务端渲染也支持通过 Web API 做前后端分离。对电商项目来说它有几个非常明显的优势。第一内置依赖注入容器。服务注册、构造函数注入、生命周期管理都不需要额外引入第三方容器项目结构天然清晰可测试性也更好。第二Razor 视图引擎配合 Tag Helper 可以快速输出 HTML同时默认做 HTML 编码减少 XSS 风险。第三模型验证和模型绑定非常成熟表单提交、查询字符串、路由数据都可以自动绑定到 C# 对象配合 DataAnnotations 就能完成大部分参数校验。第四跨平台部署Linux、Windows、Docker 都可以跑部署成本相对可控。与 Java 生态中的 Spring MVC 相比两者在概念上有相似之处比如都有 Controller、View、Model都通过前端控制器模式处理请求。区别在于 .NET Core MVC 把路由、依赖注入、日志、配置、中间件都统一到了 ASP.NET Core 这一套框架里开箱即用而 Spring MVC 通常需要叠加 Spring Boot、Spring IoC、Maven/Gradle 等一套组合。对于学习 MVC 架构的人来说掌握任何一个框架都能快速迁移到另一个关键是理解架构思想而不是背 API。1.2 MVC 架构中 Model、View、Controller 的职责边界MVC 全称是 Model-View-Controller核心思想是分离关注点。Controller 负责接收用户请求、调用业务逻辑、决定返回哪个视图View 负责展示数据也就是生成 HTMLModel 负责描述业务数据和业务规则。可以抽象成下面这个流程浏览器请求 ↓ 路由中间件匹配 URL ↓ Controller 处理请求调用 Model/Service ↓ Controller 返回 View 或 Redirect ↓ View 渲染数据生成 HTML ↓ 浏览器解析页面在这个流程中Controller 不应该写大量 SQL 或复杂业务逻辑Model 也不应该直接依赖视图。通常我们会引入 Service 层或 Repository 层让 Controller 只做参数接收、调用服务和结果转发。很多初学者容易犯的一个错误是把所有代码都写在 Controller 里一个 Action 几十行甚至上百行拆出来的方法也是私有的硬编码逻辑。短期看功能能跑但一旦业务复杂单元测试、扩展、排错都会非常吃力。后面实战部分我会展示一个相对合理的分层方式。1.3 一个容易混淆的名词MVC 的另一种含义搜索资料时经常能看到“最大值合成法(mvc)原理”这种内容这里的 MVC 指 Maximum Value Composite也就是遥感图像处理中的最大值合成法和 Web 开发里的 Model-View-Controller 完全是两个概念。如果你在做 .NET Core MVC 开发时搜到类似文章不要被带偏。本文中所有 MVC 都指 Web 架构模式 Model-View-Controller。2. 环境搭建与项目结构2.1 环境准备与版本说明本文示例以 .NET 6/7/8 常见版本为基础。你的实际 SDK 版本可能不同但只要项目创建命令能正常执行核心代码基本一致。版本需要根据你的项目实际情况调整重点演示配置思路。建议安装以下环境操作系统Windows、Linux 或 macOS 均可。.NET SDK去 .NET 官网下载对应版本的 SDK。IDEVisual Studio 2022、VS Code 或 JetBrains Rider。数据库SQL Server、SQLite 或 MySQL本地开发也可以直接用 LocalDB。提醒一点如果本机弹出类似“检测到未正确安装所需的 .NET Core 版本”的提示通常是因为某些软件自带的安装器依赖特定版本的 .NET Runtime但系统里并没有安装对应版本。解决办法是查看提示中要求的版本号从官网下载对应版本的 Runtime而不是盲目安装最新版。新版 Runtime 不一定能兼容所有老软件。2.2 创建 ASP.NET Core MVC 项目在终端中执行dotnet new mvc -n MyEcommerce cd MyEcommerce dotnet run打开浏览器访问https://localhost:5001能看到默认模板页面就说明环境正常。创建完成后项目结构大致如下MyEcommerce/ ├── Controllers/ │ └── HomeController.cs ├── Views/ │ ├── Home/ │ └── Shared/ ├── Models/ ├── wwwroot/ ├── Program.cs ├── appsettings.json └── MyEcommerce.csproj几个关键目录Controllers存放控制器类命名通常以 Controller 结尾。Views存放 Razor 视图文件结构一般和 Controller Action 对应。Models存放实体类、ViewModel、DTO。wwwroot存放静态文件如 CSS、JS、图片。Program.cs应用入口负责配置服务和中间件。.NET 6 之后的模板使用顶层语句配置集中在 Program.cs 中不再有传统的 Startup.cs。2.3 电商项目的分层规划课程中电商项目不是把所有代码都塞进 MVC 项目里而是按职责拆开。第三部分开始前建议先建立以下分层MyEcommerce.sln ├── src/ │ ├── MyEcommerce.Web // MVC 表现层 │ ├── MyEcommerce.Core // 实体、接口、领域模型 │ ├── MyEcommerce.Infrastructure // EF Core、仓储实现 │ └── MyEcommerce.Service // 业务逻辑服务如果项目规模不大也可以简化成“Web Core Infrastructure”三层。核心思想是让 MVC 项目只依赖抽象接口不直接依赖数据库访问细节这样替换数据库或写单元测试时更灵活。当然最小演示项目也可以直接把 DbContext 放到 Web 项目里先跑通功能再重构。学习阶段不要为了分层而分层但要理解为什么要分层。3. .NET Core MVC 核心机制拆解3.1 路由请求如何找到 Controller ActionMVC 默认使用约定路由。默认模板中的配置如下app.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?});这个配置的含义是URL 的第一段是 Controller 名称第二段是 Action 名称第三段是可选的 id 参数。例如请求/Product/Detail/5会匹配到ProductController的Detail(int id)方法。如果 Controller 或 Action 没有指定默认使用 Home 和 Index。也可以使用属性路由在 Controller 或 Action 上直接加[Route]特性适合 Web API 场景。[Route(Product)] public class ProductController : Controller { [Route()] [Route(Index)] public IActionResult Index() { return View(); } }路由匹配的顺序是注册顺序多个路由规则时要注意是否冲突。判断一个请求发出去之后会走哪个 Action最好的方式是启动项目后按 URL 逐个测试同时观察日志里的路由匹配结果。3.2 控制器Action 的返回类型与常用方法Controller 中的每个 public 方法就是一个 Action通常返回IActionResult类型。常用的返回方法有View()、Json()、RedirectToAction()、Content()等。public class ProductController : Controller { private readonly IProductService _productService; public ProductController(IProductService productService) { _productService productService; } public IActionResult Index() { var products _productService.GetAll(); return View(products); } public IActionResult Detail(int id) { var product _productService.GetById(id); if (product null) { return NotFound(); } return View(product); } }这里可以看到构造函数中声明了IProductService接口这是 ASP.NET Core 依赖注入的典型用法。控制器本身不关心IProductService的实现类是谁只要容器能提供对应实现框架就会在创建控制器实例时自动注入。3.3 依赖注入构造函数注入是如何把对象实例化出来的很多人在学习 .NET Core MVC 时都问过一个问题构造函数里声明了一个接口参数为什么框架能自动创建一个实现类传进来这就是依赖注入容器做的事情。依赖注入的核心思路是类的依赖不由自己 new而是由外部容器在实例化时提供。ASP.NET Core 在应用启动时会把各种服务注册到容器中当某个控制器被请求时框架的控制器激活器会创建该控制器的实例。创建过程中框架检查构造函数需要的参数类型然后从容器中逐个解析这些类型对应的服务。如果解析出来的实现类自身还有依赖容器会继续递归解析直到所有依赖都被创建出来。具体到代码层面依赖注入包含三步。第一步定义接口和实现类public interface IProductService { ListProduct GetProducts(); } public class ProductService : IProductService { public ListProduct GetProducts() { // 实际项目这里通常访问仓储或数据库 return new ListProduct(); } }第二步在 Program.cs 中注册服务builder.Services.AddScopedIProductService, ProductService();第三步在控制器构造函数中声明接口参数public class ProductController : Controller { private readonly IProductService _productService; public ProductController(IProductService productService) { _productService productService; } }当请求到达ProductController时容器会看到构造函数需要IProductService于是查找注册表发现IProductService绑定到ProductService就创建ProductService的实例传入。ASP.NET Core 中常用的注册方式有三种生命周期生命周期注册方法说明TransientAddTransient每次请求解析都创建新实例ScopedAddScoped同一个 HTTP 请求范围内共享同一个实例SingletonAddSingleton整个应用生命周期内只有一个实例选择生命周期时要结合业务场景。DbContext 通常注册为 Scoped因为每个请求应该使用同一个上下文无状态的基础工具类可以注册为 Singleton轻量服务可以注册为 Transient。如果某个接口没有在容器中注册运行时会出现类似下面的异常InvalidOperationException: Unable to resolve service for type IProductService while attempting to activate ProductController排查思路很简单检查接口是否在 Program.cs 中通过 AddScoped、AddTransient 或 AddSingleton 注册过注册的生命周期是否写错。3.4 模型绑定与模型验证当表单提交到 Action 方法时ASP.NET Core 会自动把表单字段、查询字符串、路由参数绑定到 Action 的参数对象上。这个过程叫模型绑定。public class CartItemViewModel { public int ProductId { get; set; } [Required(ErrorMessage 商品数量不能为空)] [Range(1, 999, ErrorMessage 商品数量必须在 1 到 999 之间)] public int Quantity { get; set; } }在 Action 中使用[HttpPost] public IActionResult AddToCart(CartItemViewModel model) { if (!ModelState.IsValid) { return View(model); } // 业务处理 return RedirectToAction(Index); }模型绑定会自动把表单中 name 为ProductId、Quantity的字段赋值到 model 对应属性。如果类型不对或超范围ModelState.IsValid会返回 false视图里可以通过asp-validation-for标签显示错误信息。这里要注意表单字段的 name 必须和模型属性名一致。使用 Tag Helper 可以自动维护一致性比如用asp-for生成 inputname 会根据模型属性自动生成。3.5 视图与 Tag HelperRazor 视图文件以.cshtml结尾。视图第一行通常用model声明接收的模型类型然后在 HTML 中通过Model访问数据。* 文件路径Views/Cart/Index.cshtml * model ListCartItemViewModel foreach (var item in Model) { div spanitem.ProductId/span spanitem.Quantity/span /div }Tag Helper 是 ASP.NET Core MVC 很实用的功能之一它让表单和链接生成更加类型安全。例如form asp-controllerCart asp-actionAddToCart methodpost input asp-forProductId typehidden / input asp-forQuantity classform-control / span asp-validation-forQuantity classtext-danger/span button typesubmit classbtn btn-primary加入购物车/button /form这里asp-controller和asp-action会根据路由配置自动生成 URLasp-for会根据模型属性名自动生成 name 和 id。Tag Helper 还内置了防伪令牌相关的功能在 form 标签中使用asp-controller时默认会自动生成隐藏的防伪令牌字段配合控制器上的[ValidateAntiForgeryToken]就能防 CSRF。4. 电商项目第三部分实战购物车与订单模块课程前两部分已经完成了项目搭建、商品列表和用户基础功能。第三部分的核心是购物车和订单模块。下面从模型设计开始逐步实现一个最小可用的购物车与下单流程。4.1 定义实体与数据库上下文在 Core 层定义 Product、CartItem、Order、OrderItem 实体。为了演示简洁这里把实体直接放在 Models 目录中。// 文件路径Models/Product.cs namespace MyEcommerce.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public int Stock { get; set; } } }// 文件路径Models/CartItem.cs namespace MyEcommerce.Models { public class CartItem { public int ProductId { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } public int Quantity { get; set; } public decimal TotalPrice UnitPrice * Quantity; } }// 文件路径Models/Order.cs namespace MyEcommerce.Models { public class Order { public int Id { get; set; } public string CustomerName { get; set; } public string Address { get; set; } public decimal Total { get; set; } public DateTime OrderDate { get; set; } public ListOrderItem OrderItems { get; set; } } }// 文件路径Models/OrderItem.cs namespace MyEcommerce.Models { public class OrderItem { public int Id { get; set; } public int OrderId { get; set; } public int ProductId { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } public int Quantity { get; set; } } }定义数据库上下文// 文件路径Data/ApplicationDbContext.cs using Microsoft.EntityFrameworkCore; using MyEcommerce.Models; namespace MyEcommerce.Data { public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptionsApplicationDbContext options) : base(options) { } public DbSetProduct Products { get; set; } public DbSetOrder Orders { get; set; } public DbSetOrderItem OrderItems { get; set; } } }在 Program.cs 注册 DbContextbuilder.Services.AddDbContextApplicationDbContext(options options.UseSqlServer(builder.Configuration.GetConnectionString(DefaultConnection)));对应的ConnectionStrings放在 appsettings.json 中{ ConnectionStrings: { DefaultConnection: Server(localdb)\\mssqllocaldb;DatabaseMyEcommerce;Trusted_ConnectionTrue;MultipleActiveResultSetstrue } }这里用 LocalDB 作为本地演示生产环境需要替换为正式的数据库连接串并确保密码等敏感信息不写入配置文件。4.2 实现基于 Session 的购物车服务购物车状态可以存放在 Session 中也可以存放在 Cookie或者直接放到数据库表里。对初学者来说Session 是最容易理解的方案。Session 是服务端状态默认基于内存缓存适合开发环境演示生产环境建议用 Redis 或 SQL Server 作为分布式缓存避免多实例部署时 Session 丢失。先注册 Session 相关服务// Program.cs builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options { options.IdleTimeout TimeSpan.FromMinutes(30); options.Cookie.HttpOnly true; options.Cookie.IsEssential true; }); builder.Services.AddHttpContextAccessor(); builder.Services.AddScopedICartService, CartService();中间件顺序要注意UseSession必须放在UseRouting之后、MapControllerRoute之前app.UseRouting(); app.UseSession(); app.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?});定义购物车服务接口// 文件路径Services/ICartService.cs using MyEcommerce.Models; namespace MyEcommerce.Services { public interface ICartService { ListCartItem GetItems(); void AddItem(Product product, int quantity); void UpdateQuantity(int productId, int quantity); void RemoveItem(int productId); void Clear(); decimal GetTotal(); } }实现类通过IHttpContextAccessor访问当前请求的 Session// 文件路径Services/CartService.cs using System.Text.Json; using Microsoft.AspNetCore.Http; using MyEcommerce.Models; namespace MyEcommerce.Services { public class CartService : ICartService { private readonly IHttpContextAccessor _httpContextAccessor; private const string CartSessionKey cart; public CartService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor httpContextAccessor; } private ISession Session _httpContextAccessor.HttpContext.Session; public ListCartItem GetItems() { string json Session.GetString(CartSessionKey); if (string.IsNullOrEmpty(json)) { return new ListCartItem(); } return JsonSerializer.DeserializeListCartItem(json); } public void AddItem(Product product, int quantity) { ListCartItem items GetItems(); CartItem existing items.FirstOrDefault(i i.ProductId product.Id); if (existing ! null) { existing.Quantity quantity; } else { items.Add(new CartItem { ProductId product.Id, ProductName product.Name, UnitPrice product.Price, Quantity quantity }); } string json JsonSerializer.Serialize(items); Session.SetString(CartSessionKey, json); } public void UpdateQuantity(int productId, int quantity) { ListCartItem items GetItems(); CartItem existing items.FirstOrDefault(i i.ProductId productId); if (existing ! null) { existing.Quantity quantity; if (existing.Quantity 0) { items.Remove(existing); } } Session.SetString(CartSessionKey, JsonSerializer.Serialize(items)); } public void RemoveItem(int productId) { ListCartItem items GetItems(); items.RemoveAll(i i.ProductId productId); Session.SetString(CartSessionKey, JsonSerializer.Serialize(items)); } public void Clear() { Session.Remove(CartSessionKey); } public decimal GetTotal() { ListCartItem items GetItems(); return items.Sum(i i.UnitPrice * i.Quantity); } } }这里把购物车数据序列化为 JSON 字符串存到 Session。优点是实现简单适合中小规模项目缺点是 Session 中不能存太多数据否则会占用较多服务器内存。后续如果要优化可以改成存数据库或 Redis。4.3 商品列表控制器与视图现在补上商品列表页面。这里的数据在真实项目中来自数据库为了演示流程可以先用 EF Core 查询。// 文件路径Controllers/ProductController.cs using Microsoft.AspNetCore.Mvc; using MyEcommerce.Data; using MyEcommerce.Models; namespace MyEcommerce.Controllers { public class ProductController : Controller { private readonly ApplicationDbContext _context; public ProductController(ApplicationDbContext context) { _context context; } public async TaskIActionResult Index() { ListProduct products await _context.Products.ToListAsync(); return View(products); } public async TaskIActionResult Detail(int id) { Product product await _context.Products.FindAsync(id); if (product null) { return NotFound(); } return View(product); } } }商品列表视图* 文件路径Views/Product/Index.cshtml * model ListMyEcommerce.Models.Product h2商品列表/h2 div classrow foreach (var product in Model) { div classcol-md-4 mb-3 div classcard div classcard-body h5 classcard-titleproduct.Name/h5 p classcard-textproduct.Description/p p classcard-text text-dangerproduct.Price.ToString(C)/p p classcard-text库存product.Stock/p form asp-controllerCart asp-actionAddToCart methodpost input typehidden nameproductId valueproduct.Id / input typenumber namequantity value1 min1 maxproduct.Stock classform-control mb-2 / button typesubmit classbtn btn-primary btn-sm加入购物车/button /form /div /div /div } /div这个表单直接提交到 CartController 的 AddToCart Action字段名是productId和quantity与后面 Action 参数名保持一致。4.4 购物车控制器与视图// 文件路径Controllers/CartController.cs using Microsoft.AspNetCore.Mvc; using MyEcommerce.Data; using MyEcommerce.Models; using MyEcommerce.Services; namespace MyEcommerce.Controllers { public class CartController : Controller { private readonly ICartService _cartService; private readonly ApplicationDbContext _context; public CartController(ICartService cartService, ApplicationDbContext context) { _cartService cartService; _context context; } public IActionResult Index() { ListCartItem items _cartService.GetItems(); if (items.Count 0) { ViewBag.EmptyCart true; } else { ViewBag.Total _cartService.GetTotal(); } return View(items); } [HttpPost] public async TaskIActionResult AddToCart(int productId, int quantity) { Product product await _context.Products.FindAsync(productId); if (product null) { return NotFound(); } _cartService.AddItem(product, quantity); return RedirectToAction(Index); } [HttpPost] public IActionResult Remove(int productId) { _cartService.RemoveItem(productId); return RedirectToAction(Index); } [HttpPost] public IActionResult Clear() { _cartService.Clear(); return RedirectToAction(Index); } } }购物车页面* 文件路径Views/Cart/Index.cshtml * model ListMyEcommerce.Models.CartItem h2购物车/h2 if (ViewBag.EmptyCart true) { p购物车是空的。/p a asp-controllerProduct asp-actionIndex classbtn btn-primary去商品列表/a } else { table classtable thead tr th商品/th th单价/th th数量/th th小计/th th操作/th /tr /thead tbody foreach (var item in Model) { tr tditem.ProductName/td tditem.UnitPrice.ToString(C)/td tditem.Quantity/td tditem.TotalPrice.ToString(C)/td td form asp-controllerCart asp-actionRemove methodpost input typehidden nameproductId valueitem.ProductId / button typesubmit classbtn btn-danger btn-sm删除/button /form /td /tr } /tbody /table p总计strong classtext-dangerViewBag.Total.ToString(C)/strong/p form asp-controllerCart asp-actionClear methodpost button typesubmit classbtn btn-secondary清空购物车/button /form a asp-controllerOrder asp-actionCheckout classbtn btn-success去结算/a }4.5 订单控制器与下单流程订单模块负责读取购物车数据生成订单和订单明细然后清空购物车。// 文件路径Controllers/OrderController.cs using Microsoft.AspNetCore.Mvc; using MyEcommerce.Data; using MyEcommerce.Models; using MyEcommerce.Services; namespace MyEcommerce.Controllers { public class OrderController : Controller { private readonly ICartService _cartService; private readonly ApplicationDbContext _context; public OrderController(ICartService cartService, ApplicationDbContext context) { _cartService cartService; _context context; } [HttpGet] public IActionResult Checkout() { ListCartItem items _cartService.GetItems(); if (items.Count 0) { return RedirectToAction(Index, Cart); } ViewBag.Total _cartService.GetTotal(); return View(items); } [HttpPost] public async TaskIActionResult Checkout(OrderViewModel model) { if (!ModelState.IsValid) { ListCartItem items _cartService.GetItems(); ViewBag.Total _cartService.GetTotal(); return View(items); } ListCartItem cartItems _cartService.GetItems(); var order new Order { CustomerName model.CustomerName, Address model.Address, Total _cartService.GetTotal(), OrderDate DateTime.Now, OrderItems cartItems.Select(i new OrderItem { ProductId i.ProductId, ProductName i.ProductName, UnitPrice i.UnitPrice, Quantity i.Quantity }).ToList() }; _context.Orders.Add(order); await _context.SaveChangesAsync(); _cartService.Clear(); return RedirectToAction(Success, new { orderId order.Id }); } public IActionResult Success(int orderId) { ViewBag.OrderId orderId; return View(); } } }这里需要一个 OrderViewModel 保存收货人信息// 文件路径Models/OrderViewModel.cs using System.ComponentModel.DataAnnotations; namespace MyEcommerce.Models { public class OrderViewModel { [Required(ErrorMessage 请填写收货人姓名)] [StringLength(50, MinimumLength 2, ErrorMessage 姓名长度不正确)] public string CustomerName { get; set; } [Required(ErrorMessage 请填写收货地址)] public string Address { get; set; } } }结算页面* 文件路径Views/Order/Checkout.cshtml * model ListMyEcommerce.Models.CartItem h2确认订单/h2 form asp-controllerOrder asp-actionCheckout methodpost div classform-group label forCustomerName收货人姓名/label input typetext nameCustomerName classform-control required / span asp-validation-forCustomerName classtext-danger/span /div div classform-group label forAddress收货地址/label textarea nameAddress classform-control required/textarea span asp-validation-forAddress classtext-danger/span /div table classtable mt-3 thead tr th商品/th th单价/th th数量/th th小计/th /tr /thead tbody foreach (var item in Model) { tr tditem.ProductName/td tditem.UnitPrice.ToString(C)/td tditem.Quantity/td tditem.TotalPrice.ToString(C)/td /tr } /tbody /table p订单总额strong classtext-dangerViewBag.Total.ToString(C)/strong/p button typesubmit classbtn btn-success提交订单/button /form下单成功页面* 文件路径Views/Order/Success.cshtml * { ViewData[Title] 下单成功; } h2下单成功/h2 p您的订单号是ViewBag.OrderId/p a asp-controllerProduct asp-actionIndex classbtn btn-primary继续购物/a4.6 运行与验证执行数据库迁移并启动项目dotnet ef migrations add Init dotnet ef database update dotnet run如果还没有安装 dotnet-ef 工具先安装dotnet tool install --global dotnet-ef打开浏览器按以下顺序验证访问/Product/Index能看到商品列表。点击“加入购物车”跳转到/Cart/Index。在购物车中修改数量或删除商品确认 Session 状态正确更新。点击“去结算”填写收货人姓名和地址提交订单。跳转到下单成功页面订单数据写入数据库。预期输出是购物车页面能正确显示商品列表和总额提交订单后购物车被清空数据库 Orders 和 OrderItems 表中各有一条或多条记录。如果中间某个环节报错优先查看控制台日志和浏览器开发者工具中的网络请求状态码。5. 安全与性能SQL 注入、CSRF、XSS 防护5.1 使用 EF Core 参数化查询防止 SQL 注入很多人在了解 Spring MVC 时会专门搜索“spring mvc 如何防止 sql注入”。在 .NET Core MVC 中使用 EF Core 的 LINQ 查询时框架会自动生成参数化 SQL默认就能避免 SQL 注入。例如string inputName ; DROP TABLE Products; --; var products await _context.Products .Where(p p.Name inputName) .ToListAsync();EF Core 会把这段 LINQ 转成带参数的 SQL例如SELECT * FROM Products WHERE Name p0参数值只会被当作字符串处理不会被解析成 SQL 语句因此是安全的。如果某些复杂场景必须使用原生 SQL建议使用FromSqlRaw并传出参数而不是拼接字符串// 安全写法 var products await _context.Products .FromSqlRaw(SELECT * FROM Products WHERE Name {0}, inputName) .ToListAsync(); // 危险写法绝对不要这样拼 SQL var sql SELECT * FROM Products WHERE Name inputName ;同时所有用户输入都必须先进行模型验证。控制器的参数不能盲目信任提交到服务器之前要做白名单校验、长度校验和类型校验。5.2 防 CSRF 跨站请求伪造Django、Spring MVC、ASP.NET Core MVC 都内置了 CSRF 防护机制。ASP.NET Core MVC 的做法是防伪令牌。默认情况下使用 Tag Helper 生成的 form 表单会自动携带防伪令牌字段。form asp-controllerCart asp-actionAddToCart methodpost * 这里会自动生成隐藏的防伪令牌 * input typehidden nameproductId valueproduct.Id / ... /form控制器 Action 上加上[ValidateAntiForgeryToken]特性[HttpPost] [ValidateAntiForgeryToken] public IActionResult AddToCart(int productId, int quantity) { // ... }这样即使攻击者构造了一个恶意表单诱导用户提交由于缺少正确的防伪令牌请求会被拒绝。生产环境中建议不要把[ValidateAntiForgeryToken]去掉也不要为了调试方便取消验证。5.3 输出编码与 XSS 防护Razor 视图引擎默认会对输出的内容做 HTML 编码。比如divproduct.Name/div如果product.Name中包含script标签页面上会把它显示为文本而不会执行。这是 MVC 默认的安全机制所以不要轻易使用Html.Raw()除非你非常确定内容的安全来源。5.4 数据操作的安全边界涉及订单、库存、金额等敏感数据时需要在服务端做二次校验。比如下单时要重新计算订单总额而不是直接信任前端传过来的总金额扣减库存时要考虑并发和事务用户只能操作自己的购物车不能通过篡改 session id 读取别人的数据。在生产环境做数据修改操作前一定要先备份数据库然后在测试环境执行一次完整流程。如果需要执行 UPDATE 或 DELETE 语句务必带上 WHERE 条件并确认影响行数。这是 DBA 和后端开发必须养成的习惯。6. 常见问题与排查思路以下是 .NET Core MVC 电商开发中比较常见的问题整理成表格方便查阅。问题现象常见原因解决思路Unable to resolve service for type xxx while attempting to activate yyy接口没有在依赖注入容器中注册或者注册名称拼写错误在 Program.cs 中使用 AddScoped/AddTransient/AddSingleton 注册服务页面报 500找不到视图Action 名称与视图文件名不一致或者视图没有放在正确目录检查 Views/控制器名/Action名.cshtml名称必须一一对应Session 中读不到数据没有调用 app.UseSession()或服务注册顺序不对先 AddSession 注册服务再在 UseRouting 之后调用 UseSession模型绑定后 Quantity 始终为 0表单字段 name 与模型属性名不一致使用 asp-for 标签或检查 name 属性是否对应Http 400 Bad Request防伪令牌验证失败或模型绑定类型错误检查表单是否有防伪令牌控制器是否加了 ValidateAntiForgeryTokenThe model item passed into the dictionary is of type X but requires Y视图的 model 类型和 Controller 传入的模型类型不一致确保 Controller 返回 View 时传入的对象类型和视图声明一致数据库迁移失败连接字符串错误或 EF Core 工具版本与 SDK 不匹配检查 appsettings.json运行 dotnet ef database update 前先看错误详情多实例部署时 Session 丢失默认内存 Session 不跨实例共享使用 Redis 或 SQL Server 作为 IDistributedCache 的实现排查这类问题时建议按照“看日志 → 看 URL 路由 → 看模型绑定 → 看服务注册 → 看数据库执行”的顺序。大多数 MVC 问题的错误信息已经指出了关键方向先翻译错误信息再

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号