ASP.NET Core in Practice
Middleware pipeline, dependency injection, minimal APIs vs controllers, configuration, logging, health checks and deployment.
Middleware Pipeline
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// 顺序就是请求经过的顺序(管道直通模型)
app.UseExceptionHandler(); // 1. 异常兜底
app.UseHttpsRedirection();
app.UseRouting(); // 2. 路由匹配
app.UseAuthentication(); // 3. 认证(必须先于授权)
app.UseAuthorization(); // 4. 授权
app.UseRateLimiter(); // 5. 限流
app.MapControllers(); // 6. 端点
// 自定义中间件:约定 UseXXX(this IApplicationBuilder)
app.Use(async (ctx, next) => {
var sw = Stopwatch.StartNew();
try { await next(); } // 交给下一层
finally {
logger.LogInformation("{Path} {Status} {Ms}ms",
ctx.Request.Path, ctx.Response.StatusCode, sw.ElapsedMilliseconds);
}
});
Dependency Injection
// 注册:builder.Services.AddXxx
builder.Services.AddSingleton<IClock, SystemClock>(); // 单例:全程一个
builder.Services.AddScoped<IShopDb, ShopDb>(); // 作用域:每请求一个
builder.Services.AddTransient<IEmailer, SmtpEmailer>(); // 瞬时:每次注入新建
// 构造函数注入(首选)
class OrderService(IShopDb db, IClock clock) {
public Task CreateAsync(Order o) => db.InsertAsync(o);
}
// 接口多实现:键注册 + 键取
// [FromKeyedServices("fast")] ICache cache
// 依赖关系:注册顺序无关,容器拓扑排序
// 切换实现只需改注册处(mock 测试、A/B 新实现)
Scoped services must not be injected into singletons (captive dependency); inject IServiceProvider and create a scope manually if a background service needs them.
Minimal API vs Controllers
// 最小 API:适合中小项目,一行一个端点
app.MapPost("/orders", async (CreateOrder cmd, IShopDb db, CancellationToken ct) => {
var order = new Order(cmd.UserId, cmd.Amount);
await db.InsertAsync(order, ct);
return Results.Created($"/orders/{order.Id}", order);
})
.WithName("CreateOrder")
.WithOpenApi();
// 控制器:复杂项目分层
[ApiController]
[Route("api/[controller]")]
public class OrdersController(IShopDb db) : ControllerBase {
[HttpGet("{id}")]
public async Task<ActionResult<Order>> Get(int id, CancellationToken ct) {
var o = await db.FindAsync(id, ct);
return o is null ? NotFound() : Ok(o);
}
}
// 切换:builder.Services.AddControllers(); app.MapControllers();
Config, Logging, Health Checks
builder.Configuration.AddJsonFile("appsettings.json");
builder.Configuration.AddEnvironmentVariables(prefix: "SHOP_");
// 强类型配置
builder.Services.Configure<DbOpt>(builder.Configuration.GetSection("Db"));
var logger = app.Logger; // 内置结构化日志(可用 Serilog/Seq 接管)
logger.LogInformation("user {Id} created order {OrderId}", uid, oid);
// 健康检查(K8s 探针用)
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString)
.AddUrlGroup(new Uri("https://upstream/x"), "upstream");
app.MapHealthChecks("/health"); // /health/ready /health/live 需自行分流
// IHostedService / BackgroundService:常驻任务
class OrderWorker(IShopDb db) : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken ct) {
while (!ct.IsCancellationRequested) {
await SweepExpiredAsync(ct);
await Task.Delay(TimeSpan.FromMinutes(5), ct);
}
}
}
builder.Services.AddHostedService<OrderWorker>();
Deployment Notes
- Publish with dotnet publish -c Release; run the DLL with dotnet out/Shop.dll or self-contained for offline hosts.
- ReadyToRun and NativeAOT reduce startup — NativeAOT trims reflection-heavy code, test before adopting.
- Forwarded headers middleware is required behind Nginx/K8s for correct client IPs and HTTPS scheme.
- Docker: use the mcr.microsoft.com/dotnet/aspnet:8.0 runtime image with a non-root user.
- Kestrel + Nginx reverse proxy: set limits (MaxConcurrentConnections), keep-alive timeout, and graceful shutdown via SIGTERM.
中间件管道
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// 顺序就是请求经过的顺序(管道直通模型)
app.UseExceptionHandler(); // 1. 异常兜底
app.UseHttpsRedirection();
app.UseRouting(); // 2. 路由匹配
app.UseAuthentication(); // 3. 认证(必须先于授权)
app.UseAuthorization(); // 4. 授权
app.UseRateLimiter(); // 5. 限流
app.MapControllers(); // 6. 端点
// 自定义中间件:约定 UseXXX(this IApplicationBuilder)
app.Use(async (ctx, next) => {
var sw = Stopwatch.StartNew();
try { await next(); } // 交给下一层
finally {
logger.LogInformation("{Path} {Status} {Ms}ms",
ctx.Request.Path, ctx.Response.StatusCode, sw.ElapsedMilliseconds);
}
});
依赖注入
// 注册:builder.Services.AddXxx
builder.Services.AddSingleton<IClock, SystemClock>(); // 单例:全程一个
builder.Services.AddScoped<IShopDb, ShopDb>(); // 作用域:每请求一个
builder.Services.AddTransient<IEmailer, SmtpEmailer>(); // 瞬时:每次注入新建
// 构造函数注入(首选)
class OrderService(IShopDb db, IClock clock) {
public Task CreateAsync(Order o) => db.InsertAsync(o);
}
// 接口多实现:键注册 + 键取
// [FromKeyedServices("fast")] ICache cache
// 依赖关系:注册顺序无关,容器拓扑排序
// 切换实现只需改注册处(mock 测试、A/B 新实现)
不能把 Scoped 服务注入 Singleton(被俘获依赖);后台服务需要 Scoped 时注入 IServiceProvider 并手动建作用域。
最小 API 与控制器
// 最小 API:适合中小项目,一行一个端点
app.MapPost("/orders", async (CreateOrder cmd, IShopDb db, CancellationToken ct) => {
var order = new Order(cmd.UserId, cmd.Amount);
await db.InsertAsync(order, ct);
return Results.Created($"/orders/{order.Id}", order);
})
.WithName("CreateOrder")
.WithOpenApi();
// 控制器:复杂项目分层
[ApiController]
[Route("api/[controller]")]
public class OrdersController(IShopDb db) : ControllerBase {
[HttpGet("{id}")]
public async Task<ActionResult<Order>> Get(int id, CancellationToken ct) {
var o = await db.FindAsync(id, ct);
return o is null ? NotFound() : Ok(o);
}
}
// 切换:builder.Services.AddControllers(); app.MapControllers();
配置、日志与健康检查
builder.Configuration.AddJsonFile("appsettings.json");
builder.Configuration.AddEnvironmentVariables(prefix: "SHOP_");
// 强类型配置
builder.Services.Configure<DbOpt>(builder.Configuration.GetSection("Db"));
var logger = app.Logger; // 内置结构化日志(可用 Serilog/Seq 接管)
logger.LogInformation("user {Id} created order {OrderId}", uid, oid);
// 健康检查(K8s 探针用)
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString)
.AddUrlGroup(new Uri("https://upstream/x"), "upstream");
app.MapHealthChecks("/health"); // /health/ready /health/live 需自行分流
// IHostedService / BackgroundService:常驻任务
class OrderWorker(IShopDb db) : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken ct) {
while (!ct.IsCancellationRequested) {
await SweepExpiredAsync(ct);
await Task.Delay(TimeSpan.FromMinutes(5), ct);
}
}
}
builder.Services.AddHostedService<OrderWorker>();
部署要点
- dotnet publish -c Release 发布;目标机用 dotnet Shop.dll 启动,离线环境用自包含发布。
- ReadyToRun 与 NativeAOT 加快启动——NativeAOT 会裁剪强反射代码,先验证再上。
- 在 Nginx/K8s 后必须加 Forwarded Headers 中间件,客户端 IP 与 HTTPS 协议才正确。
- Docker 用 mcr.microsoft.com/dotnet/aspnet:8.0 运行时镜像,并以非 root 用户运行。
- Kestrel + Nginx 反向代理:配好并发上限、keep-alive 超时,并用 SIGTERM 优雅关闭。