CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
ASP.NET Core 基础 MVC、Minimal API、中间件与配置。
ASP.NET Core 概览与请求管道
这一课你将能做到
用 .NET 8 CLI 创建一个可运行的 Web 项目,并解释 Program.cs 每一行在做什么
说清请求管道(中间件洋葱模型)的执行顺序,并自己写一个自定义中间件
对照 Spring Boot 说出 ASP.NET Core 在启动流程、配置、请求处理上的异同
为什么要学
ASP.NET Core 是微软官方的跨平台 Web 框架,和 Spring Boot 一样负责「收请求、调业务、回响应」。你已经懂 Spring Boot 的过滤器链、自动配置、application.yml,那这套知识可以整体迁移过来,只需要重新映射一遍词汇表:中间件(Middleware)对应 Filter + Interceptor,WebApplicationBuilder 对应 SpringApplication,appsettings.json 对应 application.yml。
本站以 Java/Spring 技术栈为主线,但在线判题平台这类系统在后端语言选型上常会讨论 .NET(判题沙箱、并发吞吐都有竞争力)。掌握 ASP.NET Core,你在面试「双栈后端」岗位、以及理解框架设计共性上,都会有一个清晰的参照系。
先建立图像
一个 HTTP 请求穿过 ASP.NET Core 应用的路径像穿过洋葱,进去一层层剥,出来原路返回:
请求 → 异常处理 → HTTPS重定向 → 路由匹配 → 端点执行
│ │ │ │
响应 ← 补异常处理 ← 补HTTPS ← 补路由 ← 返回结果
每层中间件都有机会在「请求进入前」和「响应返回后」各做一次事——这就是洋葱模型。Spring 的 Filter 链同样如此(doFilter 前后两段),但 ASP.NET Core 把它做成显式的委托管道,没有注解魔法。
启动流程对照:
Spring Boot ASP.NET Core 职责 SpringApplication.run()WebApplication.CreateBuilder() + builder.Build()构建宿主 自动扫描 + 注解注册 显式代码注册服务 容器装配 Filter / HandlerInterceptorMiddleware 横切管道 application.ymlappsettings.json配置 内嵌 Tomcat 内嵌 Kestrel Web 服务器
跟着做
步骤 1:创建最小可运行项目
安装 .NET 8 SDK 后执行:
dotnet new web -o OjHealth
cd OjHealth
dotnet run
生成的 Program.cs 全部内容就是「顶级语句」——C# 12 允许不写 Main 方法,整个文件体就是入口:
var builder = WebApplication.CreateBuilder(args); // 1. 构建宿主:读配置、装 DI 容器
var app = builder.Build(); // 2. 组装中间件管道
app.MapGet("/health", () => Results.Ok(new { status = "up" })); // 3. 注册端点
app.Run(); // 4. 启动 Kestrel,阻塞等待请求
浏览器访问 https://localhost:5001/health(端口看控制台输出),返回 {"status":"up"}。这一段代码等价于 Spring Boot 里一个 @RestController + 内嵌 Tomcat 启动的总和。
步骤 2:写一个自定义中间件 目标:给每个请求记录耗时,这是判题平台埋点的基础。
app.Use(async (context, next) =>
{
var sw = Stopwatch.StartNew();
await next(context); // 把请求交给下一层(洋葱进入内层)
Console.WriteLine($"{context.Request.Path} 耗时 {sw.ElapsedMilliseconds}ms"); // 响应回来后执行
});
放在 app.MapGet(...) 之前。注意 await next() 这一行是分界线:之前是请求阶段,之后是响应阶段。漏掉 next 请求会在这里断掉,后面的路由永远收不到请求。
步骤 3:类形态的中间件(可复用、可注入) public class TimingMiddleware(RequestDelegate next, ILogger<TimingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
await next(context);
logger.LogInformation("{Path} 耗时 {Ms}ms", context.Request.Path, sw.ElapsedMilliseconds);
}
}
注册:app.UseMiddleware<TimingMiddleware>();。主构造函数(C# 12 新语法)直接省掉了构造函数样板——构造函数注入和 Spring 的 @RequiredArgsConstructor 一个思路。
步骤 4:配置绑定(对应 Spring 的 @ConfigurationProperties) {
"Judge": {
"TimeoutSeconds": 5,
"MaxConcurrency": 8
}
}
public class JudgeOptions
{
public int TimeoutSeconds { get; set; }
public int MaxConcurrency { get; set; }
}
// Program.cs
builder.Services.Configure<JudgeOptions>(builder.Configuration.GetSection("Judge"));
// 任意服务中注入 IOptions<JudgeOptions> 即可拿到强类型配置
Spring 里这是 @ConfigurationProperties(prefix = "judge") + @EnableConfigurationProperties 的活,两者高度同构。
步骤 5:MVC 与 Minimal API 双形态 // 形态 A:Minimal API(本课程示例多用这种,短平快)
app.MapGet("/api/problems/{id:int}", (int id) =>
Results.Ok(new { id, title = "两数之和" }));
// 形态 B:Controller(大团队、大项目,结构更清晰)
[ApiController]
[Route("api/[controller]")]
public class ProblemsController : ControllerBase
{
[HttpGet("{id:int}")]
public IActionResult Get(int id) => Ok(new { id, title = "两数之和" });
}
// 注册:builder.Services.AddControllers(); app.MapControllers();
讲清楚要点
宿主(Host)是一切的地基 :WebApplicationBuilder 负责装载配置源(环境变量 > appsettings.{Environment}.json > appsettings.json)、日志、DI 容器,然后 Build() 产出不可变的 WebApplication。这和 Spring 的 ApplicationContext 生命周期概念一致:先装配,后使用,装配完容器结构不再变。
中间件顺序就是契约 :UseExceptionHandler → UseHttpsRedirection → UseAuthentication → UseAuthorization → MapXxx 的顺序不能乱。鉴权中间件放在路由后面,路由能匹配到但拿不到用户身份;放在 MapControllers 后面则鉴权根本不执行。
Kestrel 是内置 Web 服务器 :等价于内嵌 Tomcat,直接监听端口处理请求,生产环境前面通常再加 Nginx/YARP 做反向代理(对应 Spring 部署同样常见的 Nginx + Tomcat 结构)。
环境变量 ASPNETCORE_ENVIRONMENT :等价 Spring 的 spring.profiles.active,取值 Development / Staging / Production,代码里用 app.Environment.IsDevelopment() 分支。
你可能会懵的地方
为什么 ASP.NET Core 不像 Spring 那样到处是注解?
设计哲学差异。Spring 靠注解 + 类路径扫描做「约定优于配置」;ASP.NET Core 8 更倾向显式代码注册,你在 Program.cs 一眼能看全所有装配关系。用泛型主机 + 第三方包(如 Autofac 的模块扫描)也能做出注解扫描的效果。
中间件和 Controller 里常说的 Filter(如 [Authorize])是不是一回事?
不是。请求管道层的 Middleware 是框架级的、对所有请求生效;MVC 的 Filter(Action Filter、Resource Filter)和 Minimal API 的 Endpoint Filter 是「路由命中之后」的切面,粒度更细,更像 Spring 里 HandlerInterceptor 之于 Filter 的关系。
dotnet run 后控制台打印两个端口(http/https),监听规则在哪配置?
launchSettings.json(开发期)、ASPNETCORE_URLS 环境变量或 UseUrls()。等价 Spring 的 server.port。
顶级语句没有 Main 方法,程序入口到底是什么?
编译器把顶级语句文件合成为 Program.<Main>$。一个项目只允许一个文件使用顶级语句。这与 Java 21 草案的隐式类和 main 功能异曲同工。
Results.Ok() 这类返回值和 MVC 的 Ok() 什么关系?
同一协议(HTTP 语义),不同 API 门面。Minimal API 用静态类 Results,Controller 继承 ControllerBase 后用实例方法 Ok()。返回的都是 IResult / IActionResult,最终写回同一个响应流。
错法对照 错法 问题 正解 中间件里忘写 await next(...) 管道短路,后续端点收不到请求 除非刻意短路(如鉴权失败返回 401),必须调用 next app.UseAuthorization() 放在 MapControllers() 之后鉴权不生效 鉴权/授权中间件必须注册在端点映射之前 调 Build() 之后再改 DI WebApplication 装配后不可再 AddXxx所有 builder.Services.AddXxx 在 Build() 之前完成 修改代码不重启就刷新页面 热加载范围有限,改 Program.cs 结构会失效 dotnet watch 提供热重载;结构性修改保存后重启app.UseRouting() 与 MapGet 顺序凭感觉写显式写 UseRouting/UseEndpoints 时路由时机错乱 遵循 UseRouting → 中间件 → UseEndpoints 的固定顺序
卡点急救
dotnet run 报证书错误(HTTPS) :执行 dotnet dev-certs https --trust 安装并信任开发证书。
端口被占用 :launchSettings.json 改 applicationUrl,或设置环境变量 ASPNETCORE_URLS=http://localhost:5100。
改了 appsettings.json 不生效 :默认配置只在启动时读取一次;运行时刷新需要 IOptionsMonitor<T> / IOptionsSnapshot<T>(Scoped 内),不是 IOptions<T>。
面试常问
ASP.NET Core 的请求处理和 Spring Boot 的 Filter 链有何异同?
相同:都是责任链,请求按注册顺序穿入、响应按逆序穿出,前后两段可做横切逻辑。不同:ASP.NET Core 中间件是显式委托/类注册,顺序即语义且全局唯一一条链;Spring Filter 由容器按 @Order 排序,另有 MVC 层 Interceptor 二级切面。ASP.NET Core 对应 MVC 层还有 Action Filter 体系,双层结构两边一致。
Minimal API 会取代 Controller 吗?
不会互替,是场景分工。Minimal API 适合小服务、内部接口、原型(启动开销和样板更少);Controller 在模型绑定校验、版本管理、大团队分层上有成熟约定。.NET 8 里两者共享同一套路由、DI、过滤器基础设施,性能差异可忽略。
Kestrel 与 Nginx 的分工是什么?
Kestrel 处理 HTTP 协议与应用逻辑,性能很高;生产环境让 Nginx 在前面做 TLS 终结、静态资源、负载均衡与缓冲慢客户端,Kestrel 只接内网流量。这与 Java 侧 Nginx + Tomcat 的经典拓扑完全同构。
自测三问
CreateBuilder 到 app.Run() 之间发生了哪几件事?
一个请求被鉴权中间件拒绝时,洋葱模型里「响应阶段」的代码会执行吗?哪些中间件会执行?
appsettings.Production.json 和环境变量同时配置同一键,谁赢?
小结一句话 ASP.NET Core 8 = 显式装配的宿主 + 洋葱式中间件管道 + 双形态端点(Minimal API / Controller),把 Spring Boot 的启动、配置、过滤链知识按词汇表映射过来,一天即可上手。
延伸