写在前面
本文是 ASP.NET Core 学习笔记系列的第一篇。ASP.NET Core 是 .NET 后端的核心框架,但它和老的 ASP.NET(Framework)差别极大——完全重写、跨平台、高性能、模块化。很多人用着用着会觉得"能跑但不清楚为什么这么设计"。
本系列从架构层面讲透 ASP.NET Core。第一篇聚焦最核心的两件事:中间件管道和依赖注入——理解了这两个,整个框架就通了。
版本说明:基于 .NET 8 LTS。从 .NET 6 起统一用 Program.cs(top-level statements),不再有 Startup.cs。
一、ASP.NET Core 是什么
1.1 定义
ASP.NET Core 是微软的开源、跨平台、高性能 Web 框架,用来构建 Web 应用、API、微服务、实时应用(SignalR)等。
1
2
3
4
5
6
7
8
9
10
11
| 它能构建:
- Web API(RESTful 后端,最常见)
- Web 应用(MVC、Razor Pages、Blazor)
- 实时应用(SignalR、WebSocket)
- 后台服务(Worker Service)
- gRPC 服务
- 微服务
跨平台:
Windows / Linux / macOS 都能跑
部署到裸机、容器、K8s、云函数都行
|
1.2 和老 ASP.NET 的区别
1
2
3
4
5
6
7
8
9
10
| ASP.NET (Framework) ASP.NET Core
────────────────────────────────────────────────────────────────
平台 仅 Windows 跨平台
运行时 .NET Framework .NET Core / 5+
Web 服务器 IIS Kestrel(内置)+ 反向代理
配置 web.config 多源配置(json/env/命令行)
依赖注入 需第三方 内置(一等公民)
性能 一般 极高(TechEmpower 前列)
模块化 整体框架 按需引入 NuGet 包
开源 否 是(MIT)
|
1.3 设计哲学
ASP.NET Core 的所有设计围绕两个核心:
1
2
3
4
5
6
7
8
9
10
11
| 1. 中间件管道(Middleware Pipeline)
请求像流水线一样依次经过一个个中间件
每个中间件处理一件事(认证、日志、路由、CORS...)
组合优于继承,按需组装
2. 依赖注入(Dependency Injection)无处不在
所有组件通过 DI 容器管理
面向接口编程,解耦
内置 DI 容器,无需第三方
理解这两点 = 理解 ASP.NET Core 的一切
|
二、Program.cs 与启动流程
2.1 最简启动
.NET 6+ 之后,Program.cs 用 top-level statements,几行就能启动一个 API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Program.cs
var builder = WebApplication.CreateBuilder(args);
// 注册服务(DI)
builder.Services.AddControllers();
var app = builder.Build();
// 配置中间件管道
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run(); // 启动,开始监听
|
这五行做了非常多的事。拆开看。
2.2 启动流程详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| 1. WebApplication.CreateBuilder(args)
创建 WebApplicationBuilder,预配置:
- Kestrel(Web 服务器)
- 配置系统(appsettings.json、环境变量、命令行)
- 日志系统
- 依赖注入容器
- 泛型 Host
→ 返回 builder,你通过它继续配置
2. builder.Services.AddXxx()
向 DI 容器注册服务
这里注册的东西,后面能通过构造函数注入
例:AddControllers、AddDbContext、AddSwagger、AddHttpClient
3. builder.Build()
构建 WebApplication 实例
此后不能再注册服务(容器已固化)
转入"配置中间件"阶段
4. app.UseXxx() / app.MapXxx()
组装中间件管道、映射端点
顺序很重要!(后面详述)
5. app.Run()
启动应用,开始监听 HTTP 请求
阻塞主线程,直到应用停止
|
1
2
3
4
5
| 关键分水岭:builder.Build()
Build 之前 → 注册阶段(配服务、配配置)
Build 之后 → 运行阶段(配中间件、配管道)
不能在 Build 后注册服务,不能在 Build 前加中间件
|
2.3 两个阶段的清晰对照
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| var builder = WebApplication.CreateBuilder(args);
// ========== 注册阶段(builder)==========
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDb>(opt => opt.UseSqlServer("..."));
builder.Services.AddScoped<IUserService, UserService>();
builder.Logging.AddConsole();
var app = builder.Build(); // ← 分水岭
// ========== 管道阶段(app)==========
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run(); // ← 启动
|
三、中间件管道(核心)
中间件管道是 ASP.NET Core 处理请求的主干道。每个请求依次穿过所有中间件,响应再原路返回。
3.1 管道模型
1
2
3
4
5
6
7
8
9
10
| 请求 → [中间件1] → [中间件2] → [中间件3] → [端点处理] → 返回
│ │ │ │
│ │ │ │
响应 ← [中间件1] ← [中间件2] ← [中间件3] ←─────┘
每个中间件:
- 处理请求(进)→ 调用下一个 → 处理响应(出)
- 可以决定是否调用下一个(短路)
像洋葱模型:层层进入,层层出来
|
3.2 三种写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 写法一:app.Use —— 最通用,可以调用下一个
app.Use(async (context, next) =>
{
// 进:处理请求(before)
Console.WriteLine("请求进来");
await next(); // 调用下一个中间件
// 出:处理响应(after)
Console.WriteLine("响应出去");
});
// 写法二:app.Run —— 终结中间件,不调用下一个(管道终点)
app.Run(async context =>
{
await context.Response.WriteAsync("Hello");
});
// 写法三:app.Map —— 按路径分支
app.Map("/api", apiApp =>
{
apiApp.Run(async ctx => await ctx.Response.WriteAsync("API"));
});
|
3.3 顺序非常重要
中间件的注册顺序决定执行顺序,错了会导致功能异常:
1
2
3
4
5
6
7
8
9
10
11
| // ✅ 正确顺序(官方推荐)
app.UseExceptionHandler(); // 1. 异常处理(最外层,兜底)
app.UseHsts(); // 2. HSTS
app.UseHttpsRedirection(); // 3. HTTP→HTTPS
app.UseStaticFiles(); // 4. 静态文件
app.UseRouting(); // 5. 路由匹配
app.UseCors(); // 6. CORS
app.UseAuthentication(); // 7. 认证(谁是谁)
app.UseAuthorization(); // 8. 授权(能干什么)
app.UseSession(); // 9. Session(按需)
app.MapControllers(); // 10. 端点执行
|
1
2
3
4
5
6
7
8
9
10
11
| 为什么顺序重要:
UseRouting 必须在 UseAuthorization 之前
→ 先匹配出端点,才能对端点做授权检查
UseAuthentication 必须在 UseAuthorization 之前
→ 先认证身份(填充 User),才能判断权限
UseExceptionHandler 必须在最前面
→ 任何中间件抛异常都能被它兜住
记忆:异常 → 协议 → 静态 → 路由 → CORS → 认证 → 授权 → 端点
|
3.4 自定义中间件
复杂中间件建议封装成类,而不是内联 lambda:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // 自定义中间件类(约定:有 InvokeAsync 方法)
public class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTimingMiddleware> _logger;
public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
try
{
await _next(context); // 调下一个
}
finally
{
sw.Stop();
_logger.LogInformation("{Method} {Path} 耗时 {Ms}ms",
context.Request.Method, context.Request.Path, sw.ElapsedMilliseconds);
}
}
}
// 扩展方法(优雅注册)
public static class RequestTimingExtensions
{
public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder app)
{
return app.UseMiddleware<RequestTimingMiddleware>();
}
}
// Program.cs 里用
app.UseRequestTiming();
|
四、依赖注入(核心)
ASP.NET Core 的 DI 是一等公民——所有组件都通过 DI 管理。理解 DI 是写出松耦合、可测试代码的关键。
4.1 三种生命周期
1
2
3
4
5
6
7
8
9
10
11
12
| // 1. Transient —— 每次请求都新建(最轻量,最常用)
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
// 每次注入都拿新实例
// 2. Scoped —— 每个 HTTP 请求一个(请求内共享)
builder.Services.AddScoped<IUserService, UserService>();
// 同一个 HTTP 请求内拿到同一个实例,请求结束释放
// EF Core 的 DbContext 默认是 Scoped
// 3. Singleton —— 全局唯一(应用生命周期)
builder.Services.AddSingleton<IClock, SystemClock>();
// 所有请求共享一个实例,应用停止才释放
|
1
2
3
4
5
6
7
8
| 生命周期选择:
Transient → 轻量、无状态的服务
Scoped → 需要在一次请求内共享(DbContext、当前用户上下文)
Singleton → 全局共享、线程安全、重资源(缓存、配置、连接池)
⚠️ 陷阱:
Singleton 不能依赖 Scoped 服务(Scoped 在请求间释放,Singleton 持有会出错)
反过来可以:Scoped 可以依赖 Singleton/Transient
|
4.2 注册方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 接口到实现
builder.Services.AddScoped<IUserService, UserService>();
// 多实现(注入时用 IEnumerable<IUserService> 拿全部)
builder.Services.AddScoped<INotifier, EmailNotifier>();
builder.Services.AddScoped<INotifier, SmsNotifier>();
// 工厂注册(需要复杂构造)
builder.Services.AddScoped<IConnectionFactory>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new ConnectionFactory(config.GetConnectionString("Db"));
});
// 自身注册(不需要接口)
builder.Services.AddScoped<UserService>();
// TryAdd(不存在才注册,避免覆盖)
builder.Services.TryAddScoped<IUserService, UserService>();
|
4.3 注入方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // 构造函数注入(推荐,最常用)
public class UserController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UserController> _logger;
public UserController(IUserService userService, ILogger<UserController> logger)
{
_userService = userService;
_logger = logger;
}
}
// [FromServices] 注入(Minimal API、单个方法用)
app.MapGet("/users", ([FromServices] IUserService svc) => svc.GetAll());
// IServiceProvider 手动解析(少用,需要时)
public class Worker
{
private readonly IServiceProvider _sp;
public Worker(IServiceProvider sp) => _sp = sp;
public void DoWork()
{
using var scope = _sp.CreateScope(); // 手动创建 Scope
var svc = scope.ServiceProvider.GetRequiredService<IUserService>();
svc.DoSomething();
}
}
|
4.4 一个完整示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| // 接口
public interface IUserService
{
Task<User> GetUserAsync(int id);
}
// 实现
public class UserService : IUserService
{
private readonly AppDbContext _db; // Scoped
private readonly ICache _cache; // Singleton
private readonly ILogger<UserService> _logger;
// 构造函数注入三个依赖
public UserService(AppDbContext db, ICache cache, ILogger<UserService> logger)
{
_db = db; _cache = cache; _logger = logger;
}
public async Task<User> GetUserAsync(int id) => await _db.Users.FindAsync(id);
}
// 注册
builder.Services.AddDbContext<AppDbContext>(); // Scoped
builder.Services.AddSingleton<ICache, MemoryCache>(); // Singleton
builder.Services.AddScoped<IUserService, UserService>(); // Scoped
// 使用
public class UserController : ControllerBase
{
private readonly IUserService _userService;
public UserController(IUserService userService) => _userService = userService;
[HttpGet("{id}")]
public async Task<User> Get(int id) => await _userService.GetUserAsync(id);
}
|
五、配置系统
ASP.NET Core 的配置非常强大——多源、分层、强类型。
5.1 配置源
1
2
3
4
5
6
7
8
9
10
| // 默认按顺序读取(后者覆盖前者):
// 1. appsettings.json
// 2. appsettings.{Environment}.json (如 appsettings.Production.json)
// 3. 用户机密(开发环境,secrets.json)
// 4. 环境变量
// 5. 命令行参数
// 读取配置
var connStr = builder.Configuration.GetConnectionString("DefaultConnection");
var name = builder.Configuration["AppName"];
|
1
2
3
4
5
6
7
8
9
10
11
12
| // appsettings.json
{
"AppName": "MyBlog",
"ConnectionStrings": {
"DefaultConnection": "Server=...;Database=...;"
},
"Jwt": {
"Issuer": "jiwei.space",
"ExpiresMinutes": 60,
"Secret": "..."
}
}
|
5.2 强类型选项(Options 模式)
不要到处用字符串 key 读配置,用强类型 Options:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // 1. 定义配置类
public class JwtOptions
{
public string Issuer { get; set; }
public int ExpiresMinutes { get; set; }
public string Secret { get; set; }
}
// 2. 绑定
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
// 3. 注入使用
public class TokenService
{
private readonly JwtOptions _jwt;
public TokenService(IOptions<JwtOptions> options) => _jwt = options.Value;
public string CreateToken()
{
// _jwt.Issuer / _jwt.ExpiresMinutes / _jwt.Secret
}
}
|
1
2
3
4
5
| IOptions<T> — 单例,启动时绑定,值固定
IOptionsSnapshot<T> — 每个请求重新绑定(配置热更新生效)
IOptionsMonitor<T> — 单例,但能监听变化
需要配置热更新 → 用 IOptionsSnapshot
|
六、日志
6.1 基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
| public class UserService
{
private readonly ILogger<UserService> _logger;
public UserService(ILogger<UserService> logger) => _logger = logger;
public void Login(string username)
{
_logger.LogInformation("用户 {Username} 登录", username);
_logger.LogWarning("用户 {Username} 登录失败次数过多", username);
_logger.LogError(ex, "登录异常 {Username}", username);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| 日志级别(从低到高):
Trace < Debug < Information < Warning < Error < Critical
生产通常设 Information 或 Warning(过滤掉 Debug/Trace)
结构化日志(重要):
✅ _logger.LogInformation("用户 {Username} 登录", username);
→ 占位符 {Username} 是结构化字段,日志系统能按字段检索
❌ _logger.LogInformation($"用户 {username} 登录");
→ 字符串插值,丢失结构化信息,无法按字段查询
|
6.2 配置
1
2
3
4
5
6
7
8
9
| // appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
|
1
2
3
4
| // Program.cs 加日志提供程序
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.AddSeq(); // Serlog Seq 等
|
七、Host 与 Kestrel
7.1 泛型 Host
ASP.NET Core 跑在泛型 Host 之上,统一管理:
1
2
3
4
5
6
7
8
9
| Host 管理的东西:
- 依赖注入容器
- 配置
- 日志
- 生命周期(启动、停止)
- 后台服务(IHostedService)
WebApplication.CreateBuilder 自动配好了一个带 Web 功能的 Host
Worker Service 用纯 Host(无 Web)
|
7.2 Kestrel(内置 Web 服务器)
1
2
3
4
5
6
7
8
9
10
11
12
13
| Kestrel 是 ASP.NET Core 自带的跨平台 Web 服务器:
✓ 跨平台(Windows/Linux/macOS)
✓ 极高性能(基于 Pipelines、异步全链路)
✓ 内置,不需要 IIS/Nginx 也能跑
生产部署架构:
客户端 → Nginx(反向代理,HTTPS)→ Kestrel(HTTP)
为什么前面要 Nginx:
- HTTPS 终端(Kestrel 处理 HTTP)
- 静态文件、缓存、限流
- 更成熟的安全防护
(详见本站 Nginx 系列)
|
1
2
3
4
5
6
| // 配置 Kestrel
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxConcurrentConnections = 10000;
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10MB
});
|
八、环境与配置
8.1 环境标识
1
2
3
4
5
6
7
8
| 通过环境变量 ASPNETCORE_ENVIRONMENT 设置:
Development — 开发(本地)
Staging — 预发布
Production — 生产
不同环境加载不同配置文件:
Development → appsettings.json + appsettings.Development.json
Production → appsettings.json + appsettings.Production.json
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 代码里判断环境
var env = builder.Environment;
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseDeveloperExceptionPage(); // 开发显示详细错误
}
else
{
app.UseExceptionHandler("/error"); // 生产用友好错误页
app.UseHsts();
}
|
8.2 启动配置
1
2
3
4
5
6
7
8
| # 设置环境
export ASPNETCORE_ENVIRONMENT=Production
# 设置监听端口
export ASPNETCORE_URLS=http://0.0.0.0:5000
# 启动
dotnet run
|
九、小结
本文是 ASP.NET Core 系列的开篇,建立了整体认知:
- 定位:跨平台、高性能、模块化的现代 Web 框架
- 两大支柱:中间件管道(处理请求)+ 依赖注入(管理组件)
- 启动流程:
CreateBuilder → 注册服务 → Build → 配置中间件 → Run - 中间件管道:请求像洋葱层层穿过,顺序非常重要
- 依赖注入:三种生命周期(Transient/Scoped/Singleton),构造函数注入
- 配置:多源 + 强类型 Options 模式
- 日志:ILogger 结构化日志
- Host/Kestrel:泛型 Host 管理生命周期,Kestrel 是高性能内置服务器
核心心法:理解了中间件管道和依赖注入,ASP.NET Core 就理解了一半。 后续所有功能(路由、认证、EF Core)都是建立在这两个基础上的。
下一篇将讲路由与控制器:URL 如何匹配到代码、数据如何绑定、请求如何验证。