这次我们来看一个 .NET Core MVC 电商实战项目项目标签是 E-commerce MVC内容可以看作一套电商系统的第三阶段实现。到了这个阶段项目重点已经不是“搭架子”而是把商品、购物车、订单、支付模拟、后台管理这些核心业务串起来同时把依赖注入、安全防护、接口 API 这些工程化问题处理干净。对于想完整跑通 .NET Core MVC 电商项目的人来说这套代码拆得比较清楚Controller 只做请求分发Service 层负责业务规则Repository 或 EF Core 负责数据访问Razor 视图负责展示。技术栈不花哨但很实用。这个项目最值得关注的点有这么几个一是依赖注入全部走构造函数注入Service 和 Controller 的依赖关系非常明确二是所有数据访问都在 EF Core 里完成天然走参数化查询只要不在代码里手动拼接 SQLSQL 注入风险就能压下来三是项目里同时保留 Razor 页面和 JSON API 控制器网页端和接口调用都能覆盖四是支持通过 dotnet CLI 一键运行本地只要有 .NET SDK 和数据库不需要额外装大型 IDE 就能跑起来。硬件门槛很低没有 GPU 需求普通开发机即可流畅运行。这篇文章会按实际开发顺序带读者走一遍先搭环境、初始化项目、配置数据库和依赖注入然后启动服务验证注册登录、商品浏览、购物车、下单这一整条链路再单独讲解 MVC 架构里的依赖注入实现方式和防 SQL 注入的写法最后演示如何调用 JSON API、观察资源占用、排查常见问题。适合刚学完 C# 基础、想用 .NET Core MVC 做一个完整电商系统的开发者也适合准备用这个项目做毕业设计或内部系统参考的同学。1. .NET Core MVC 电商项目核心能力速览在做任何二次开发之前先看这个项目的整体规格。能力项说明项目类型ASP.NET Core MVC Web 应用电商业务场景技术栈C#、ASP.NET Core MVC、EF Core、Razor 视图、Bootstrap核心模块用户注册登录、商品管理、购物车、订单、后台管理运行环境需要安装 .NET SDK建议 8.0 及以上版本具体以项目目标框架为准数据库SQL Server / LocalDB / SQLite 均可通过连接字符串切换启动方式dotnet run命令启动Web 界面默认通过 Kestrel 提供访问接口 API支持控制器使用[ApiController]返回 JSON 数据批量任务可扩展商品批量导入可写成一次性的控制台脚本或后台任务依赖注入构造函数注入接口与实现分离安全能力EF Core 参数化查询、Razor 默认编码、防 CSRF、身份认证适合场景学习 MVC 分层、复习依赖注入、练习电商业务闭环、开发内部系统从这张表能看出这个项目不是那种需要大量 GPU 资源的 AI 模型项目它的运行依赖很简单核心是 .NET 运行时和数据库环境。更稳妥的判断是只要本机能创建并运行一个 ASP.NET Core 项目这套电商代码就能跑起来。2. 适用场景与使用边界这个项目适合什么人能解决什么问题需要先弄清楚。首先它适合想完整理解 MVC 架构的开发者。很多教程只讲“Controller 返回一个 View”但这个项目会把商品列表、详情、购物车、订单这些业务按照 Service 层重新组织代码结构更接近真实项目。跟着项目跑一遍能看清浏览器请求是怎么经过 Controller、Service、Repository 最后回到 View 的。其次它适合作毕业设计或内部管理系统的参考。电商系统包含用户、商品、订单、后台权限这一类通用业务把这些模块抽出来改造成图书管理系统、设备管理系统、预约系统都容易。特别是后台管理部分的角色判断逻辑是很多内部系统都会用到的。但它不适合作为大型高并发商城的生产基础。项目里没有分布式缓存、消息队列、分库分表也没有对接真实支付网关的完整实现。如果业务目标是支撑大量并发下单需要在架构层面重新设计。使用边界方面这里要特别提醒几点。如果项目代码来自某个课程或开源仓库使用前要确认许可证和版权要求不能把课程源码直接打包商用涉及用户手机号、密码、订单信息时必须最小化采集密码用 Identity 默认哈希存储日志里不要输出敏感字段生产环境部署前要补上 HTTPS、隐私政策、数据备份方案。总的来说这个项目适合做技术验证和功能参考直接上线商用前还需要做一轮安全加固。3. 环境准备与前置条件在正式搭建项目之前先把运行环境检查一遍。下面这些前置条件不是可选项缺一个就会在某个环节卡住。操作系统选 Windows、Linux、macOS 都可以.NET Core 是跨平台的。内存建议 8GB 以上磁盘预留 5GB 左右就够主要用于安装 SDK、NuGet 缓存和数据库文件。IDE 可以用 Visual Studio 2022、VS Code 或 Rider不用 IDE 也可以直接用命令行工具。主要依赖项包括.NET SDK 8.0 或 9.0具体版本以项目的TargetFramework为准数据库SQL Server Express LocalDB 或 SQLite二选一Git拉取项目代码和版本管理用Postman 或 curl测试 JSON API 用EF Core 工具执行数据库迁移用打开终端先确认 .NET SDK 是否安装成功dotnet --info如果输出里能看到SDK版本信息说明环境正常。如果提示找不到命令需要先安装 .NET SDK。再执行dotnet --list-sdks查看本机实际安装了哪些 SDK。如果你在启动项目时遇到“未正确安装所需的 .NET Core 版本 8”类型的报错多半是项目的目标框架是 net8.0但本机缺少对应的 .NET Core Runtime 或 SDK。此时不要盲目改项目框架先去官网安装对应版本再重新运行。数据库方面如果选择 LocalDB需要确认本机安装了 SQL Server Express LocalDB。可以直接在命令行测试sqllocaldb info如果输出有实例名说明 LocalDB 可用。如果选择 SQLite则不需要额外装服务连接字符串改一下即可。对于学习用途SQLite 是最省事的方案。4. 项目初始化与部署启动这一节从零开始搭建一个可运行的 .NET Core MVC 电商项目。如果你已经有一份现成源码可以直接跳到 4.2 配置数据库并启动。4.1 创建 MVC 项目打开终端进入希望存放代码的目录执行dotnet new mvc -n MyECommerce cd MyECommerce这个命令会生成一个标准的 ASP.NET Core MVC 项目包含Controllers、Models、Views文件夹。接下来添加 EF Core 和 Identity 相关的包dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore如果使用 SQLite把第一个包换成Microsoft.EntityFrameworkCore.Sqlite即可。添加包之后项目文件MyECommerce.csproj里会出现如下类似内容这是 NuGet 依赖的正常体现不需要手动改Project SdkMicrosoft.NET.Sdk.Web PropertyGroup TargetFrameworknet8.0/TargetFramework Nullableenable/Nullable ImplicitUsingsenable/ImplicitUsings /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.EntityFrameworkCore.SqlServer Version8.0.x / PackageReference IncludeMicrosoft.EntityFrameworkCore.Tools Version8.0.x / PackageReference IncludeMicrosoft.AspNetCore.Identity.EntityFrameworkCore Version8.0.x / /ItemGroup /Project4.2 配置数据库连接字符串打开appsettings.json把连接字符串写进去。下面以 SQL Server LocalDB 为例{ ConnectionStrings: { DefaultConnection: Server(localdb)\\MSSQLLocalDB;DatabaseECommerceDb;Trusted_ConnectionTrue;MultipleActiveResultSetstrue }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * }如果使用 SQLite可以改成{ ConnectionStrings: { DefaultConnection: Data Sourceecommerce.db } }连接字符串不要硬编码在代码里后续要部署到不同环境时可以通过环境变量或 user-secrets 覆盖。4.3 注册 DbContext 和自定义服务在Program.cs里注册 DbContext同时注册商品、购物车、订单服务。下面是一个典型的注册片段using Microsoft.EntityFrameworkCore; var builder WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); builder.Services.AddDbContextAppDbContext(options options.UseSqlServer(builder.Configuration.GetConnectionString(DefaultConnection))); builder.Services.AddScopedIProductService, ProductService(); builder.Services.AddScopedICartService, CartService(); builder.Services.AddScopedIOrderService, OrderService(); var app builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(/Home/Error); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?}); app.Run();这里的AddScoped是关键。它告诉依赖注入容器每个 HTTP 请求周期内创建一次服务实例。IProductService和ProductService的关系会在第 6 节详细说明。4.4 添加实体和迁移电商项目至少需要商品、购物车、订单、订单明细这几张表。这里给一个简化的AppDbContext定义public class AppDbContext : DbContext { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } public DbSetProduct Products { get; set; } public DbSetCartItem CartItems { get; set; } public DbSetOrder Orders { get; set; } public DbSetOrderItem OrderItems { get; set; } }首次启动前需要创建数据库迁移dotnet ef migrations add Init dotnet ef database update如果没有安装 EF Core 工具先执行dotnet tool install --global dotnet-ef4.5 启动服务所有配置完成之后直接用命令启动dotnet run默认情况下Kestrel 会监听https://localhost:5001和http://localhost:5000实际端口以终端输出为准。如果你在 Visual Studio 中启动端口由launchSettings.json决定。下面是一个自定义端口的示例{ profiles: { MyECommerce: { commandName: Project, launchBrowser: true, applicationUrl: https://localhost:7100;http://localhost:5100, environmentVariables: { ASPNETCORE_ENVIRONMENT: Development } } } }启动后浏览器访问http://localhost:5100能看到项目首页说明基础环境已经跑通。5. 功能测试与效果验证项目能启动只是第一步真正要验证的是电商业务能不能形成闭环。这一节按功能模块拆开测试每个模块都给出操作步骤、预期结果和排查方向。5.1 用户注册与登录打开浏览器访问/Account/Register创建一个测试账号。这里要注意如果项目使用 ASP.NET Core Identity密码会有复杂度要求通常需要包含大写字母、数字和特殊字符。注册成功后系统一般会自动登录并跳转到首页。接着退出登录再访问/Account/Login用刚才的账号登录。判断标准登录成功后导航栏能显示当前用户名访问需要授权的页面时未登录用户会被重定向到登录页。如果注册后一直报密码不符合要求按页面提示调整密码强度即可。如果点击登录没有反应先看终端日志里有没有数据库连接错误这类问题通常是迁移未执行导致。5.2 商品列表与搜索访问/Products或首页商品列表确认种子数据能正常展示。如果数据库里没有数据需要先通过迁移种子数据或手动插入。商品搜索是电商项目里最容易出问题的地方。测试时可以输入一个数据库中不存在的关键词再输入一个存在的词。预期是无结果时显示“未找到商品”提示有结果时返回匹配列表。这里重点观察搜索是否使用参数化查询。如果代码里写成string.Join拼接 SQL搜索框就变成了 SQL 注入入口。正确写法会在第 7 节给出。5.3 加入购物车在商品详情页点击“加入购物车”然后打开购物车页面确认商品名称、单价、数量、小计都正确。购物车的存储方式常见有两种一种是存到 Cookie 或 Session适合未登录用户另一种是存到数据库适合已登录用户。测试时注意两点未登录时加入购物车登录之后购物车数据是否还在同一商品重复添加数量是叠加还是新建一条记录如果刷新页面后购物车消失基本上可以判断 Session 配置或 Cookie 中间件有问题。检查Program.cs里是否配置了 Session 服务。5.4 下单与模拟支付从购物车点击“结算”系统会生成订单和订单明细同时需要扣减商品库存。这一步是电商项目的高频 bug 点建议重点测试正常流程余额充足库存充足下单成功库存不足购买数量大于库存提示失败并回滚重复提交连续点击两次“提交订单”不能生成两笔订单预期结果是订单创建成功购物车被清空库存数量减少。如果发现重复提交产生多笔订单可以给提交按钮加禁用状态并在服务端判断“待支付订单”是否已存在。库存扣减在并发场景下需要关注。简单项目可以依赖数据库更新时的行锁或WHERE Stock quantity条件判断事务里先扣库存再创建订单失败则回滚。5.5 管理后台后台管理页面一般要求管理员角色才能访问。测试时先创建一个普通用户再创建一个管理员用户分别访问/Admin。判断标准普通用户访问后台被拒绝或跳转登录页管理员用户能正常查看商品管理、订单管理页面。如果普通用户也能进入后台说明[Authorize(Roles Admin)]特性没有加到控制器上。这种权限校验逻辑要放在控制器或 action 上不能只靠前端隐藏按钮实现。5.6 测试结果汇总下面这张表可以作为项目验收清单测试项预期结果失败排查方向用户注册注册成功并登录Identity 配置、密码策略、数据库迁移用户登录登录后显示用户名Cookie 认证、数据库连接商品列表正常展示商品数据种子数据、EF Core 查询商品搜索返回匹配商品参数化查询、关键字处理加入购物车购物车数量正确Session/数据库存储方式提交订单订单生成、库存扣减事务、库存校验后台权限非管理员无法访问[Authorize(Roles Admin)]6. MVC 架构与依赖注入实现这个项目的核心架构是 MVC但真正让代码可维护的是依赖注入。下面分别展开。6.1 MVC 请求处理流程MVC 架构可以理解为一条请求流水线浏览器把 HTTP 请求发送到 ControllerController 不直接写数据库它调用 Service 层的方法Service 层再通过 Repository 或 EF Core 操作数据库最后把数据打包成 Model 交给 View 渲染。在实际项目中流程通常是这样的浏览器请求 - Controller 接收请求参数 - Service 执行业务规则 - EF Core / Repository 操作数据库 - Service 返回结果给 Controller - Controller 选择 View 或 Json 返回Controller 本身不要写太多业务代码否则会变成“胖控制器”。更好的做法是Controller 只做参数绑定、调用 Service、返回结果Service 里放校验规则、库存判断、订单计算等业务逻辑。6.2 构造函数注入的工作原理依赖注入解决的核心问题是“类的依赖从哪来”。如果不使用依赖注入你可能会在 Controller 里写new ProductService()这会让 Controller 和 ProductService 强耦合替换实现或写单元测试都很麻烦。使用构造函数注入之后ProductController不再关心IProductService是如何创建的而是声明自己需要一个IProductService由依赖注入容器在请求进来时自动创建并传入。注册核心服务builder.Services.AddScopedIProductService, ProductService(); builder.Services.AddScopedICartService, CartService(); builder.Services.AddScopedIOrderService, OrderService();控制器构造函数注入public class ProductController : Controller { private readonly IProductService _productService; private readonly ILoggerProductController _logger; public ProductController(IProductService productService, ILoggerProductController logger) { _productService productService; _logger logger; } public async TaskIActionResult Index() { var products await _productService.GetActiveProductsAsync(); return View(products); } }这个过程理解起来可以这样看当用户请求Product/Index时MVC 框架发现ProductController的构造函数需要IProductService于是它去依赖注入容器里查找注册信息发现IProductService对应ProductService就把ProductService实例化后传给构造函数。如果ProductService本身还需要AppDbContext容器会继续递归创建AppDbContext直到整条依赖链完整。6.3 生命周期选择依赖注入服务有三种常见生命周期AddScoped每个 HTTP 请求创建一次适合 DbContext、业务 ServiceAddTransient每次获取都创建新实例适合轻量无状态服务AddSingleton整个应用只创建一个实例适合缓存、配置读取在电商项目中DbContext 必须使用 Scoped 或 Transient不能使用 Singleton因为 EF Core 的 DbContext 不是线程安全的。业务 Service 推荐 Scoped这样在一个请求内共享同一个实例既避免无谓创建又能与 DbContext 保持相同生命周期。7. 防 SQL 注入与安全配置安全这部分防 SQL 注入是重点但也不能只看 SQL 注入。Razor 的 XSS 防护、CSRF 防护、身份认证都需要一起检查。7.1 为什么 EF Core 能防 SQL 注入EF Core 会把 LINQ 表达式树转换成参数化 SQL。比如下面这个搜索方法var products _context.Products .Where(p p.Name.Contains(keyword) || p.Description.Contains(keyword)) .ToList();EF Core 生成的 SQL 大概是SELECT * FROM Products WHERE Name LIKE __keyword_0 OR Description LIKE __keyword_1keyword被作为参数传入而不是拼到 SQL 字符串里。即使用户在搜索框输入 OR 11 --也只会被当成普通字符串去匹配不会改变 SQL 语句结构。7.2 原生 SQL 的正确写法有时候因为性能需要必须手写原生 SQL。EF Core 提供了FromSqlInterpolated可以做到参数化var keyword % searchTerm %; var products _context.Products .FromSqlInterpolated($SELECT * FROM Products WHERE Name LIKE {keyword}) .ToList();注意不能写成这样var sql SELECT * FROM Products WHERE Name LIKE % searchTerm %; var products _context.Products.FromSqlRaw(sql).ToList();FromSqlRaw直接接受字符串存在 SQL 注入风险尽量避免使用。如果确实需要动态拼接表名、排序字段不要接受用户输入直接拼接要使用白名单映射。7.3 输入验证与 XSS 防护在 Model 上添加数据注解是防输入问题的第一道防线public class Product { public int Id { get; set; } [Required(ErrorMessage 商品名称不能为空)] [StringLength(100)] public string Name { get; set; } [Range(0.01, 1000000)] public decimal Price { get; set; } [StringLength(2000)] public string Description { get; set; } }Razor 视图默认会对输出内容做 HTML 编码。也就是说用户提交的scriptalert(1)/script会被原样显示为文本不会作为 HTML 执行。除非你明确知道内容安全否则不要使用Html.Raw()。7.4 CSRF 与身份认证MVC 项目里所有 POST 表单都应该添加防跨站请求伪造校验。Controller 的 action 加上[ValidateAntiForgeryToken]Razor 表单里加上Html.AntiForgeryToken()这是标配。[HttpPost] [ValidateAntiForgeryToken] public async TaskIActionResult Add(int productId, int quantity) { await _cartService.AddAsync(productId, quantity); return RedirectToAction(Index); }身份认证方面如果项目使用 ASP.NET Core Identity登录后默认通过 Cookie 维持会话。生产环境必须启用 HTTPS否则用户密码和会话 Cookie 都可能被中间人截获。8. 接口 API 与批量任务电商项目除了网页端一般还需要给小程序、App 或前后端分离架构提供 JSON API。项目里的 API 控制器可以单独创建一个Api文件夹避免和页面控制器混在一起。8.1 商品查询 API下面是一个常见的 Products API 控制器示例[ApiController] [Route(api/[controller])] public class ProductsController : ControllerBase { private readonly IProductService _productService; public ProductsController(IProductService productService) { _productService productService; } [HttpGet] public async TaskIActionResult Get([FromQuery] string? keyword) { var products await _productService.GetProductsAsync(keyword); return Ok(products); } [HttpGet({id:int})] public async TaskIActionResult GetById(int id) { var product await _productService.GetProductByIdAsync(id); if (product null) { return NotFound(); } return Ok(product); } }启动项目后用 curl 验证curl -X GET http://localhost:5100/api/products用 Python 调用同样可以import requests resp requests.get(http://localhost:5100/api/products, timeout10) if resp.status_code 200: for item in resp.json(): print(item[id], item[name], item[price]) else: print(请求失败:, resp.status_code)接口能跑通之后就可以把商品数据接到自己的前端项目里或者做一个小程序端的商品展示页。8.2 批量导入商品电商后台很常见的需求是批量导入商品。一种做法是上传 CSV 文件在服务端逐行读取并写入数据库。下面是一个简化的导入逻辑public async Task ImportProductsAsync(IFormFile file) { using var reader new StreamReader(file.OpenReadStream(), Encoding.UTF8); await reader.ReadLineAsync(); // 跳过表头 while (!reader.EndOfStream) { var line await reader.ReadLineAsync(); if (string.IsNullOrWhiteSpace(line)) { continue; } var fields line.Split(,); if (fields.Length 3) { _logger.LogWarning(跳过无效行: {Line}, line); continue; } var product new Product { Name fields[0].Trim(), Price decimal.Parse(fields[1].Trim()), Stock int.Parse(fields[2].Trim()) }; _context.Products.Add(product); } await _context.SaveChangesAsync(); }注意几点文件编码建议固定为 UTF-8避免中文乱码每一行都要做字段数量和格式校验单条数据失败不能影响整批导入要把失败行记录到日志并继续处理批量插入时如果数据量很大可以按每 500 条分批SaveChanges减少数据库事务压力。8.3 定时后台任务如果项目需要自动关闭超时未支付的订单可以用IHostedService或BackgroundService实现一个简单的定时任务public class OrderTimeoutWorker : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILoggerOrderTimeoutWorker _logger; public OrderTimeoutWorker(IServiceScopeFactory scopeFactory, ILoggerOrderTimeoutWorker logger) { _scopeFactory scopeFactory; _logger logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope _scopeFactory.CreateScope(); var db scope.ServiceProvider.GetRequiredServiceAppDbContext(); var timeoutOrders await db.Orders .Where(o o.Status OrderStatus.Pending o.CreatedAt DateTime.Now.AddMinutes(-15)) .ToListAsync(stoppingToken); foreach (var order in timeoutOrders) { order.Status OrderStatus.Cancelled; _logger.LogInformation(订单 {OrderId} 超时关闭, order.Id); } await db.SaveChangesAsync(stoppingToken); await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } } }这里需要在Program.cs注册后台服务builder.Services.AddHostedServiceOrderTimeoutWorker();后台任务的周期要结合业务需求来定不需要跑得太频繁每分钟一次通常够用。9. 资源占用与性能观察.NET Core MVC 项目不像 AI 推理那样吃显存它主要消耗的是 CPU、内存和数据库连接。这一节重点说明怎么观察这些指标以及如何通过简单手段优化接口响应。9.1 内存与 CPU 观察启动dotnet run后可以用任务管理器查看进程占用。启动初期内存占用通常在 100MB 到 300MB 左右这个数字会受项目引用的 NuGet 包数量影响。如果长期运行后内存不断上涨优先检查是否有静态集合在持续添加数据或者 EF Core 的查询是否没有释放 IQueryable。CPU 占用飙升通常和循环查询、N1 查询有关。比如在循环里每次都执行一次数据库查询商品数量多了以后 CPU 和数据库都会很吃力。9.2 EF Core SQL 日志打开 EF Core 的 SQL 日志能看到每个接口实际执行了几条 SQL。在appsettings.Development.json中启用{ Logging: { LogLevel: { Default: Information, Microsoft.EntityFrameworkCore.Database.Command: Information } } }日志里会输出类似下面的内容可以直接看到 SQL 语句和对参数Executing DbCommand (12ms) [Parameters[__keyword_0耳机], CommandTypeText, CommandTimeout30] SELECT [p].[Id], [p].[Name], [p].[Price] FROM [Products] AS [p] WHERE [p].[Name] LIKE __keyword_0如果发现某个页面执行了几十条 SQL几乎可以断定有 N1 查询问题。解决方式是在查询时使用Include预加载导航属性或者在不需要跟踪实体时使用AsNoTrackingvar orders await _context.Orders .Include(o o.OrderItems) .ThenInclude(oi oi.Product) .AsNoTracking() .ToListAsync();9.3 缓存商品列表商品列表通常是读多写少适合加缓存。项目里可以直接使用内存缓存builder.Services.AddMemoryCache();在 Service 里使用public async TaskListProduct GetActiveProductsAsync() { if (_cache.TryGetValue(active_products, out ListProduct? cached)) { return cached ?? new ListProduct(); } var products await _context.Products .Where(p p.IsActive) .AsNoTracking() .ToListAsync(); _cache.Set(active_products, products, TimeSpan.FromMinutes(5)); return products; }缓存适合低频变动的数据。商品库存这种高频变动的数据不要缓存太久否则用户看到的库存数量和实际库存会不一致。9.4 接口响应时间测试接口响应时间可以用 curl 的-w参数快速观察curl -w 耗时: %{time_total}s\n -o /dev/null -s http://localhost:5100/api/products第一次请求可能因为 JIT 编译和初始化较慢连续请求几次之后再看平均耗时。如果接口响应时间明显偏慢优先检查 SQL 执行次数和是否有大字段传输。10. 常见问题与排查方法本地跑 .NET Core MVC 项目常见问题集中在这几类环境问题、数据库问题、路由问题和权限问题。下面整理了一张排查表按“现象 - 原因 - 排查 - 解决”的顺序写。问题现象可能原因排查方式解决方案启动后页面打不开端口被占用或服务未启动查看终端输出地址检查端口监听更换 Kestrel 端口杀掉占用进程后重启数据库连接失败连接字符串错误或数据库服务未启动查看终端日志中的异常信息核对连接字符串改用 SQLite 快速验证dotnet ef命令不存在未安装 EF Core 工具执行dotnet tool list -g安装dotnet-ef全局工具迁移时报“已有表”数据库已有部分表查看迁移历史表删除数据库后重新执行database update登录后请求 500数据库没有迁移或 Cookie 配置错误查看运行时异常堆栈执行迁移检查UseAuthentication接口返回 401/403未登录或角色不足查看请求是否带 CookieToken 是否过期登录后调用或为接口添加允许匿名访问配置报“未正确安装所需的 .NET Core 版本 8”本机缺少对应 Runtime/SDK执行dotnet --list-sdks安装匹配版本的 SDK或调整目标框架批量导入中文乱码文件编码不是 UTF-8用编辑器查看文件编码另存为 UTF-8 编码后重新上传商品搜索卡顿查询没有索引数据量大查看 EF Core SQL 日志给Name字段添加索引后台页面普通用户也能访问缺少[Authorize(Roles Admin)]检查控制器和 action 特性在控制器上补充角色验证遇到问题时最快的定位方式是看终端里的异常堆栈。.NET Core 的异常信息通常非常明确首次 error 堆栈一般就是真正的原因不需要先怀疑框架。11. 最佳实践与使用建议电商 MVC 项目想从“能跑”到“能维护”下面这些点值得在开发过程中就落实。先跑通最小闭环。第一次运行项目时不要一次把支付、物流、退款全做完先把注册 - 商品列表 - 购物车 - 下单 - 后台查看订单这条主线跑通确认数据库表和页面跳转没问题再扩展其他功能。把连接字符串放到环境变量或 user-secrets 中。appsettings.json里的连接字符串会在代码提交时进入版本库容易造成数据库地址泄露。开发环境可以用dotnet user-secrets init dotnet user-secrets set ConnectionStrings:DefaultConnection Server...;Database...;Trusted_ConnectionTrue;然后让配置系统读取。发布到服务器时使用环境变量覆盖连接字符串。保持 Service 层不依赖 HttpContext。Service 层如果直接访问HttpContext.Session会让业务逻辑和 Web 环境耦合。正确做法是 Controller 从请求中解析出用户 ID 和参数传给 Service。在敏感操作里加审计日志。商品价格修改、订单状态变更、用户角色调整都应该记录操作人、操作时间和变更前后值。日志不要记录密码明文、完整手机号等敏感信息。批量任务一定要加失败重试。如果导入商品时第 100 行出错而前面 99 行已经写入数据库要么整体事务回滚要么记录错误行并继续处理。对生产环境来说逐条记录失败原因比一次性抛出异常更容易排查。接口服务要限制访问范围。如果 API 只给内部系统用可以在Program.cs里限制只监听本机地址或者通过反向代理控制访问 IP。公开接口则要加认证和限流。涉及人脸、声音、版权素材等情况时务必先确认授权。虽然本文没涉及这些功能但电商项目如果后续要上传商品图片、用户头像也要注意不要使用无版权的网络图片避免侵权风险。12. 总结与下一步这个 .NET Core MVC 电商项目最值得尝试的地方是把一个完整的业务闭环用清晰的分层架构实现出来。Controller、Service、EF Core 三层各司其职依赖注入用构造函数方式贯穿始终这让项目在功能扩充时不会越改越乱。拿到项目后建议先验证三个功能注册登录是否正常、商品列表和搜索是否走参数化查询、购物车到订单的流程是否会正确扣减库存。这三个点验证通过项目的核心骨架就已经掌握。最容易踩的坑集中在数据库环境上。EF Core 迁移失败、连接字符串写错、本机缺少对应 .NET Runtime这三类问题占了本地部署阶段的大多数报错。遇到时先看异常堆栈通常一行错误信息就能指出问题方向。后续可以继续扩展的方向不少引入 Redis 缓存提升商品页响应速度对接支付网关沙箱环境把模拟支付替换成真实支付流程给 Service 层补单元测试确保订单计算和库存扣减逻辑稳定用 Docker 把项目和数据库一起打包部署到 Linux 服务器。如果你想深入学习可以把项目里的商品模块先完整读一遍从 Controller 到 Service 再到 EF Core 查询理解一条请求的完整路径。然后尝试自己动手加一个“商品分类”功能从建表到页面展示走一遍完整流程这样比单纯看代码收获更大。