ASP.NET Core in Practice

Middleware pipeline, dependency injection, minimal APIs vs controllers, configuration, logging, health checks and deployment.

IntermediateUpdated 2026-10-3058K views

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.

Friends & Partners