ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ASP.NET Core 6 Web API实战:从零搭建到部署的完整指南

2026/9/8 8:25:23 拓冰建站 浏览量
ASP.NET Core 6 Web API实战:从零搭建到部署的完整指南 简介示例项目基于 ASP.NET Core 6.0 与 Entity Framework Core 构建主要用于演示 RESTful 风格 Web API 的开发方式面向具备一定 C# 基础并希望快速上手接口工程的读者也适合课堂教学或内部培训场景。内容覆盖项目初始化、程序入口中的服务注册与中间件配置再到数据上下文的实体映射以及控制器中的增删改查实现同时内置数据迁移目录能够直观理解数据模型与数据库表之间的对应关系。压缩包内文件总数为十六个以 C# 源代码为主体共九个文件另外包含三个 JSON 配置文件、解决方案文件与项目文件以及 Git 相关辅助配置整体大小约十三KB结构非常精简。项目采用控制器、模型、数据迁移等典型分层组织入口与扩展点清晰便于快速定位和阅读学习者可以在此脚手架基础上继续加入身份认证、日志记录、异常处理等机制。该示例已获得三百余人学习浏览作为入门样例或项目起步模板都较为合适尤其适合希望通过精简代码掌握依赖注入、路由特性与异步编程组合用法的开发者。1. 为什么选 ASP.NET Core 6 做 Web API先聊聊整体思路这些年做后端接口我前后用过不少技术栈从早期的 Web Service、WCF到后来 Node.js 的 Express、Python 的 FastAPI再到 Java 系的 Spring Boot各有各的脾气。兜兜转转最后在 ASP.NET Core 上稳定了下来。如果你正在为API 项目到底用什么框架纠结我的建议很直接中小团队、Windows 或 Linux 环境都跑得起来的项目ASP.NET Core 6 是目前综合性价比相当高的选择——性能排在热门框架第一梯队生态成熟开箱即用的功能也远比多数人想象的多。这个示例项目的定位很简单用 .NET 6 的 LTS 版本搭建一个完整的 Web API 服务覆盖从项目创建、数据模型定义、仓储模式、依赖注入、接口路由到 Swagger 文档、日志、部署配置的完整闭环。它不是那种只写一个 Hello World 的玩具项目而是一个能直接拿去做二次开发、甚至做生产环境脚手架的基准代码库。适合谁看我分三类刚入门 .NET 的人跟着步骤把整个 API 跑起来理解 Controller、DTO、EF Core 这些核心概念是怎么串起来的从 .NET Framework 老项目迁移过来的人很多老代码用的是 ASP.NET Web API 2.x 或 WCF这套示例能帮你快速建立 .NET 6 时代的代码组织方式做架构选型、写技术方案的人想确认 ASP.NET Core 6 在 Web API 场景下能覆盖哪些能力踩过哪些坑。先说结论.NET 6 的 Web API 开发体验比 5.x 更像现代框架也比传统 .NET Framework 时代清爽太多。用一句话总结——它把接口写起来方便和架构搭起来规范这两件事平衡得很好。下面我把整个搭建过程、关键代码的为什么这么做、以及我在实际项目中踩过的坑完整拆给你。1.1 技术选型背后的三个考量项目里我会用到这么一套组合ASP.NET Core 6 EF Core 6 SQLite开发环境 Swagger接口文档。为什么这么选为什么要 LTS 版本.NET 6 是微软的长期支持版本官方的支持周期会持续到 2024 年 11 月现已进入维护阶段但生产环境存量依然巨大。相比 .NET 5 这类非 LTS 版本6 适合作为企业级应用的底座。对于从零开始的新项目如果团队没有特殊理由我通常会先看 LTS。为什么用 EF Core 而不是 Dapper这个示例项目侧重清晰的 CRUD 数据关系映射EF Core 有一套完整的迁移机制写起来代码量少模型和数据库可以保持同步。如果后续遇到复杂查询性能瓶颈可以用 raw SQL 甚至切 Dapper但示例阶段 EF Core 的学习曲线最平缓。为什么开发环境用 SQLite零配置、单文件非常适合做演示和本地调试不需要安装数据库服务。真正部署上线切到 SQL Server 或 PostgreSQL 时只需要改连接字符串和少量 EF Core 配置项。这套组合不是什么黑科技但它是目前 .NET 生态里文档最全、坑最少、提问时最容易搜到答案的方案。初学阶段别追求花哨把最常见的技术栈吃透比什么都强。1.2 动手之前先想清楚的三件事正式开始写代码前有三件事我建议你先想清楚不然代码写一半很容易返工。第一接口风格是走 RESTful 还是 RPC很多新手的项目就是单纯为了交差URL 直接写/api/getUserById这种动词式接口。RESTful 风格更注重资源本身的表述常见做法是/api/users/{id}。我这个示例里用的是 RESTful 风格——用 HTTP 的 GET、POST、PUT、DELETE 表达动作URL 里只有资源名词没有动词。第二响应格式和错误码怎么统一项目一旦变大A 接口返回{ code: 0, data: ... }B 接口返回业务对象本身C 接口出错时直接抛 HTTP 500三个接口三种格式前端对接的人想骂人。我在这套示例里做了两层处理正常情况直接返回ActionResultT让框架自动返回 200 和序列化结果业务异常被全局异常过滤器捕获统一包装成{ code, message, detail }格式返回。前端只需要处理两种成功/失败的 JSON 结构简单明了。第三DTO 和实体能不能直接用同一个类初学者经常图省事直接把User实体返回给前端。这么做短期看没问题实体里的密码哈希、内部状态字段、导航属性等敏感信息很容易被意外序列化出去。这个示例里我所有的接口入口和出口统一都走 DTO可以理解成传输专用模型。多写一个 DTO 类不费事但能省掉大量安全问题。2. 环境准备与项目骨架搭建2.1 开发环境与 SDK 安装细节先说环境。你需要先确认自己电脑上装了 .NET 6 SDK。这里有个容易踩的坑很多人机器上装了多个版本的 SDK用dotnet --version检查时看到的可能不是 6.x。打开终端Windows 用 PowerShell 或 CMDmacOS/Linux 用自带的 shell依次执行dotnet --list-sdks dotnet --list-runtimes第一条命令是看 SDK 版本列表第二条是看运行时版本。比如这样6.0.428 [C:\Program Files\dotnet\sdk]如果--list-sdks里什么也没有或者没有 6.x 开头的那一行去官方下载页面下载对应的 SDK 安装包按默认路径安装就行。装完之后重新开一个终端窗口再执行一次dotnet --version确认这一步能避免环境变量没有刷新的经典尴尬。另外我建议直接装最新的 6.0 补丁版本比如 6.0.4xx 系列而不要停留在 6.0.100 这类很老的版本——后续补丁不仅修复了大量安全问题对开发工具的兼容性也更好。2.2 两种项目形态怎么选Controller 还是 Minimal API.NET 6 同时支持两种写 Web API 的方式传统的 Controller 方式和 .NET 6 推出的 Minimal API最小 API方式。Minimal API代码量大幅减少所有逻辑可以直接写在Program.cs里的 lambda 中。适合微服务里的极简接口、简单的健康检查、或者是演示 Demo。Controller 方式每个接口按类 方法组织天然支持模型绑定、模型验证、过滤器、依赖注入等 MVC 功能适合业务逻辑较多的项目和团队协作。我这个示例用的是Controller 方式。原因很实际大多数公司的生产项目都有复杂的业务验证、多表事务、权限过滤等需求Controller 的模块化程度更高多人改代码时也更不容易冲突。Minimal API 很好但它更适合小而美的场景这个项目是给你做手脚架的不是做玩具的所以我不会用 Minimal API 来组织全部业务代码。2.3 命令行创建项目打开终端找一个你喜欢的目录执行dotnet new webapi -n TodoApi cd TodoApi这里的webapi是模板名称TodoApi是项目名创建完毕后目录里会多出一个TodoApi.csproj、Program.cs、Controllers文件夹等。此时你运行dotnet run浏览器或者 Postman 里访问https://localhost:7xxx/swagger就能看到 Swagger UI 页面里面默认带着一个叫WeatherForecast的示例接口。这就是脚手架自带的开箱体验后面我们要把示例代码逐步替换成自己的业务代码。提示如果你更习惯用 Visual Studio创建项目时选 ASP.NET Core Web API 模板勾选使用控制器或者最小 API选项即可效果一样。VS Code 用户装好 C# 插件后用命令行创建项目再code .打开体验也很顺滑。3. 核心功能实现从数据模型到接口完成接下来进入正题。我用一个经典的待办事项场景来演示 CRUD我的目标是做完一个TodoItem的一套增删改查接口。3.1 定义实体模型和数据库上下文首先建一个Models文件夹定义实体类namespace TodoApi.Models; public class TodoItem { public long Id { get; set; } public string? Title { get; set; } public bool IsComplete { get; set; } public DateTime CreatedAt { get; set; } DateTime.UtcNow; public DateTime? UpdatedAt { get; set; } }这里有个小经验CreatedAt默认值用DateTime.UtcNow而不要用DateTime.Now。UTC 时间是全球统一标准存库的时候用 UTC展示的时候再转本地时间这个习惯能帮你在后续部署到云服务器、出现时间差问题时少掉一把头发。接着在项目根目录建Data文件夹定义 DbContextusing Microsoft.EntityFrameworkCore; using TodoApi.Models; namespace TodoApi.Data; public class AppDbContext : DbContext { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } public DbSetTodoItem TodoItems SetTodoItem(); }然后创建迁移让数据库跟模型保持一致。迁移是 EF Core 的核心能力之一它会把 C# 模型翻译成对应的数据库表结构变更脚本不需要你手写 SQL。在项目根目录执行dotnet ef migrations add InitialCreate dotnet ef database updateInitialCreate是迁移名称可以自定义。执行完项目里会出现一个Migrations文件夹里面记录了模型的变化历史开发环境的 SQLite 数据库文件也会自动生成。以后模型改了只需要新增迁移并更新数据库即可这个流程是团队协作时保证数据库结构一致的最靠谱方案。3.2 控制器里实现标准 CRUDProperties/launchSettings.json是开发环境启动配置。模板项目里通常能明显看到两个入门知识点applicationUrl开发环境下默认监听的地址和端口比如http://localhost:5000;https://localhost:7001。实际工作中要按你的需求调整。environmentVariables比如ASPNETCORE_ENVIRONMENT: Development它控制的环境变量会决定appsettings.Development.json里配置项是否能生效。关于环境变量我有句话要说不要在源码里写死任何包含密钥、连接字符串敏感信息的配置。开发环境可以用appsettings.Development.json里的User Secrets生产环境走环境变量或者云平台的托管配置服务比如 Azure App Service 配置、Docker Secret 等。这是底线问题不是麻烦问题。这段是示例的硬核部分创建接口、列表接口、修改接口、删除接口全部用上面这些知识写在一个 Controller 里。using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TodoApi.Data; using TodoApi.Models; namespace TodoApi.Controllers; [ApiController] [Route(api/[controller])] public class TodoItemsController : ControllerBase { private readonly AppDbContext _context; public TodoItemsController(AppDbContext context) { _context context; } // GET: /api/TodoItems [HttpGet] public async TaskActionResultIEnumerableTodoItem GetTodoItems() { return await _context.TodoItems.ToListAsync(); } // GET: /api/TodoItems/5 [HttpGet({id})] public async TaskActionResultTodoItem GetTodoItem(long id) { var item await _context.TodoItems.FindAsync(id); if (item null) { return NotFound(); } return item; } // POST: /api/TodoItems [HttpPost] public async TaskActionResultTodoItem PostTodoItem(TodoItem item) { item.CreatedAt DateTime.UtcNow; _context.TodoItems.Add(item); await _context.SaveChangesAsync(); return CreatedAtAction(nameof(GetTodoItem), new { id item.Id }, item); } // PUT: /api/TodoItems/5 [HttpPut({id})] public async TaskIActionResult PutTodoItem(long id, TodoItem item) { if (id ! item.Id) { return BadRequest(); } item.UpdatedAt DateTime.UtcNow; _context.Entry(item).State EntityState.Modified; await _context.SaveChangesAsync(); return NoContent(); } // DELETE: /api/TodoItems/5 [HttpDelete({id})] public async TaskIActionResult DeleteTodoItem(long id) { var item await _context.TodoItems.FindAsync(id); if (item null) { return NotFound(); } _context.TodoItems.Remove(item); await _context.SaveChangesAsync(); return NoContent(); } }网络热词里提到了海康综合安防管理平台 web 取 HLS 流这类场景其实思路是相通的——如果平台方提供了 HTTP 接口作为鉴权入口你在 ASP.NET Core 里做一个代理接口从平台拿到 HLS 流地址再返回给前端播放器本质上就是一个带鉴权逻辑的 GET 接口跟上面 Controller 的写法结构一致。Web API 的优势就在这里不管是内部系统对接还是物联网设备数据上报接口层就是一套统一进出管道。3.3 依赖注入的来龙去脉ASP.NET Core 这套框架从设计上就极度依赖依赖注入。所谓依赖注入白话讲就是你要用的东西别人已经准备好了你只需要声明你想要的类型框架会把它递给你。在Program.cs里面注册builder.Services.AddDbContextAppDbContext(options options.UseSqlite(builder.Configuration.GetConnectionString(DefaultConnection))); builder.Services.AddControllers();在 Controller 构造函数里你只需要写AppDbContext context作为参数框架会自动根据上面的注册把实例传进来。这就叫构造器注入。好处是测试友好想替换成内存数据库、模拟数据时只需要改注册部分不需要大改业务逻辑。作用域可控DbContext 默认在请求级作用域内共享同一个 HTTP 请求内每次注入拿到同一个实例避免了并发冲突。实际开发中我还会把强业务逻辑拆成 Service 类Controller 只做 取参数、调 Service、返回结果 这三件事不直接碰 EF Core。Service 也通过依赖注入注册代码干净很多。示例项目里没拆得过细但思路你应该感受到了——依赖注入就是在帮你降低模块间的耦合度。3.4 必备的模型验证与统一返回值如果你直接把上一步的PostTodoItem跑起来你会发现传一个空Title也能创建成功。生产环境这是不可接受的。加验证最快的方式是用 DataAnnotationspublic class TodoItem { public long Id { get; set; } [Required(ErrorMessage 标题不能为空)] [StringLength(100, MinimumLength 2, ErrorMessage 标题长度必须在2到100个字符之间)] public string? Title { get; set; } public bool IsComplete { get; set; } }加了[Required]、[StringLength]特性后再次 POST 一个空标题ASP.NET Core 会自动返回 400 Bad Request响应体里是验证失败的详细信息列表前端拿这个信息渲染错误提示就行。但这里有个细微的问题默认返回的错误信息结构是每个字段一个错误数组前端处理起来略麻烦。所以我在实际项目中通常会加一个自定义的ApiResponse包装类把它统一成{ code: 400, message: 请求参数错误, errors: { title: 标题不能为空 } }具体实现不在这里展开但你可以通过自定义ApiControllerAttribute的行为或者全局异常过滤器来实现。核心思路是让成功返回的数据和失败返回的错误有统一的结构这是 API 团队协作时最容易忽略却最重要的一环。4. 文档、日志与部署让它具备上线底气4.1 Swagger 自动生成 API 文档模块里自带 Swagger 配置但模板默认的配置只能覆盖基本场景生产环境里我还会做这几件事按接口版本分组文档如果/api/v1和/api/v2共存Swagger 里可以配置多个OpenApiInfo分别展示各组接口。XML 注释生成在.csproj里开启GenerateDocumentationFile然后在 Controller 和方法上写summary注释Swagger 会直接显示每个接口的说明文字。这一步付出的成本极低但对同事、对甲方、对前端的价值却很高。.Program.cs里的构建最核心的是这段if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }注意IsDevelopment()这个条件。发布到生产环境后默认不会再暴露出 Swagger 页面避免接口结构被随意查看。如果需要再在测试环境用通过环境变量控制即可。4.2 日志不是可选配置ASP.NET Core 里默认集成的日志框架是 ILogger。你可以在任意类里注入它public class TodoItemsController : ControllerBase { private readonly ILoggerTodoItemsController _logger; public TodoItemsController(AppDbContext context, ILoggerTodoItemsController logger) { _context context; _logger logger; } }然后在接口里按级别打日志比如_logger.LogInformation(查询了全部待办事项总数{Count}, items.Count);打日志的核心经验是什么不要在日志里打印密码、token、身份证号等敏感信息。这属于我踩过之后最刻骨铭心的坑之一——线上排查问题时看到了明文 token还得紧急改进日志框架和脱敏配置。其次日志不是越多越好但核心业务操作创建、修改、删除、异常一定要有痕迹。4.3 发布与部署常见配置发布到生产环境前至少要把这几件事处理到位连接字符串开发环境的 SQLite 连接字符串放在appsettings.Development.json生产环境通过环境变量覆盖例如ConnectionStrings__DefaultConnectionServer...;Database...;User Id...;Password...;这种双下划线写法能让 ASP.NET Core 的配置系统自动识别环境变量不需要改代码。生成发布包dotnet publish -c Release -o ./publish这条命令会输出一个publish文件夹里面是编译好的程序和依赖。部署到 Linux 上可以用 Nginx 反代到 Kestrel 端口或者直接用 Docker 容器化。我实际部署时优先走 Docker原因很简单环境一致性省心回滚也方便。HTTPS 强制跳转模板里自带了UseHttpsRedirection()开发环境不一定需要强制但生产环境务必保证所有对外请求走 TLS。如果你在反向代理层Nginx/IIS已经做了 HTTPS 终结点那代码里的 HTTPS 重定向可以去掉避免内网重定向死循环。5. 常见问题与排查技巧实录写 Web API 项目最耗时间的往往不是写业务是排坑。我把这几年遇到频率最高、也最值得写下来的几个问题整理成了一张速查表现象可能原因排查方法接口访问返回 404路由没匹配上Controller 类不是 public构造函数注入的类型没注册先保证浏览器能打开/swagger检查[Route]、[HttpXxx]的特性看 startup 日志里的路由匹配过程返回 500 内部错误绝大多数是空引用异常或 EF Core 数据库连接异常看应用日志日志里通常会有堆栈信息如果日志没有先把app.Environment.EnvironmentName手动切到 Development 观察返回 400 但是看不出哪里错了模型验证失败检查 DTO 上的 DataAnnotations 特性检查请求参数的content-type是否是application/jsonEF Core 迁移生成了但更新数据库报错模型跟数据库不一致数据库连接字符串配置错误执行dotnet ef migrations script查看生成的 SQL核对数据库是否真的执行成功CORS 跨域导致前端调接口失败前端域名与 API 域名不同.AddCors().UseCors()配置允许的域名注意开发环境别直接放行所有域5.1 最容易被忽略的隐式 using问题.NET 6 默认开启了隐式 using也就是说Program.cs里的using System;、using System.Collections.Generic;这些都不用写编译器会自动加上。表面看起来省事但对刚接触 .NET 6 的人来说有个小坑当你新建一个类文件时如果不显式写using Microsoft.EntityFrameworkCore;很可能出现找不到类型 DbContext的编译错误。我的建议是依赖完整显式 using 表达。IDE 会自动提示补全加上并不费事但代码的可读性和可移植性会好很多。尤其当团队有人在用 Rider 或 VS Code自动补全行为不完全一致时显式 using 能少一些莫名其妙的 using 去哪了 问题。5.2 线上接口日志太多磁盘爆了怎么办另一个真实案例项目上了日志框架之后默认配置把每次请求的 body 和 response 全打了出来生产环境跑了三天磁盘直接打满接口开始卡顿。排查之后发现是日志级别配置过于宽松。解决方案是分级配置{ Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning, Microsoft.EntityFrameworkCore.Database.Command: Error } } }这样应用程序的Information日志会保留但框架级和 EF Core 的 SQL 命令日志只在错误级别时才输出磁盘空间稳定了很多。这个细节没人教你只能踩坑踩出来。最后再讲一个我自己的习惯这个示例项目本身并不复杂但如果你能照着跑一遍、把每个文件都改一改、再自己加一个分类功能效果会比只看一遍文档好十倍。我自己的习惯是每新建一个 .NET 项目第一件事就是把通用封装统一响应、异常处理、日志、Swagger 配置整理成一套模板存成私有的项目模板。下次开新项目的时候直接dotnet new调用省掉大量重复劳动。另外建议你在本地装一个 REST Client 工具比如 Postman、Insomnia或者 VS Code 的 REST Client 插件。调用接口、调试参数时比 Swagger UI 灵活得多尤其调试带 token 的接口和复杂 POST 请求时效率差别非常明显。如果遇到模型和数据库对不上、迁移失败、或者接口 500 的问题先不要盲目改代码第一步永远是看日志——90% 的谜底都藏在日志里。把日志事件 ID、异常堆栈贴到搜索引擎解决方案基本都有了。这套项目的道和术都在上面了剩下的就是打开终端亲手把它跑起来。本文还有配套的精品资源点击获取