Minimal API:轻量高效的路由定义
Minimal API 是 .NET 6 引入的路由注册风格,无需 Controller 类,直接在 Program.cs 中用 Lambda 定义端点。它比传统 Controller 有更低的启动时间和内存开销,非常适合微服务和小型 API。
var builder = WebApplication.CreateBuilder(args);
// 注册服务
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
// 路由组(共享前缀和过滤器)
var products = app.MapGroup("/api/products")
.RequireAuthorization() // 整组需要认证
.WithTags("Products") // OpenAPI 分组
.WithOpenApi();
products.MapGet("/", async (IProductService svc, CancellationToken ct) =>
Results.Ok(await svc.GetAllAsync(ct)));
products.MapGet("/{id:int}", async (int id, IProductService svc) =>
{
var product = await svc.GetByIdAsync(id);
return product is null
? Results.NotFound()
: Results.Ok(product);
});
products.MapPost("/", async (
CreateProductRequest req,
IProductService svc,
LinkGenerator links) =>
{
var created = await svc.CreateAsync(req);
return Results.CreatedAtRoute("GetProduct", new { id = created.Id }, created);
});
app.Run();
Controller-based API:企业级组织
对于大型项目,Controller 方式更易于组织和维护,支持过滤器、属性路由、模型验证等特性。
[ApiController]
[Route("api/[controller]")]
public class UsersController(IUserService userService) : ControllerBase
{
// [ApiController] 自动:参数绑定、模型验证、400 响应
[HttpGet]
[ProducesResponseType(typeof(List<UserDto>), 200)]
public async Task<IActionResult> GetAll(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken ct = default)
{
var users = await userService.GetPagedAsync(page, pageSize, ct);
return Ok(users);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateUserRequest req)
{
// ModelState 验证由 [ApiController] 自动处理
var user = await userService.CreateAsync(req);
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
}
// 请求 DTO(带数据注解验证)
public record CreateUserRequest(
[Required, MaxLength(100)] string Name,
[Required, EmailAddress] string Email,
[Range(18, 120)] int Age
);
中间件管道
ASP.NET Core 的请求处理是一个管道,每个中间件可以选择处理请求、修改响应,或调用 next 传递给下一个中间件。
请求 → [HTTPS Redirect]
[Static Files]
[Routing]
[Authentication]
[Authorization]
[Rate Limiting]
[自定义中间件 A]
[自定义中间件 B]
[Endpoint Handler] ← 实际处理逻辑
[自定义中间件 B] ← 响应返回时反向执行
[自定义中间件 A]
...
← 响应
// 自定义中间件(请求耗时记录)
public class RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
try
{
await next(context);
}
finally
{
sw.Stop();
logger.LogInformation(
"{Method} {Path} → {Status} in {Ms}ms",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
sw.ElapsedMilliseconds);
}
}
}
// 注册中间件
app.UseMiddleware<RequestTimingMiddleware>();
依赖注入:三种生命周期
Singleton(单例)
整个应用生命周期只创建一个实例,所有请求共享。适用于无状态的工具类、配置对象、HttpClient(通过 IHttpClientFactory)、内存缓存等。注意:Singleton 中不能注入 Scoped 服务(会导致 Captive Dependency 问题)。
Scoped(作用域)
每个 HTTP 请求(或显式的 scope)创建一个实例,在该请求内共享。Entity Framework 的 DbContext 必须是 Scoped——保证一个请求内多个操作共享同一个数据库连接和事务上下文。
Transient(瞬态)
每次注入时创建新实例。适合轻量级、无状态的服务。如果一个 Transient 服务持有非托管资源(如数据库连接),必须实现 IDisposable,DI 容器在 scope 结束时会调用 Dispose。
builder.Services.AddSingleton<IMemoryCache, MemoryCache>();
builder.Services.AddScoped<AppDbContext>(); // EF Core DbContext
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
// 工厂注册(带逻辑)
builder.Services.AddSingleton<ILogger>(sp =>
sp.GetRequiredService<ILoggerFactory>().CreateLogger("App"));
// HttpClient(内置连接池管理)
builder.Services.AddHttpClient<IGitHubClient, GitHubClient>(client =>
{
client.BaseAddress = new Uri("https://api.github.com");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp");
}).AddStandardResilienceHandler(); // .NET 8+ 内置重试/熔断
配置系统
// appsettings.json
{
"Database": {
"ConnectionString": "Host=localhost;Database=app",
"MaxRetry": 3
},
"Jwt": {
"SecretKey": "${JWT_SECRET}",
"ExpiryMinutes": 60
}
}
// 强类型配置(Options Pattern)
public class JwtOptions
{
public const string Section = "Jwt";
public string SecretKey { get; set; } = string.Empty;
public int ExpiryMinutes { get; set; } = 60;
}
// 注册
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection(JwtOptions.Section));
// 注入使用
public class TokenService(IOptions<JwtOptions> options)
{
private readonly JwtOptions _opt = options.Value;
// ...
}
全局异常处理
// .NET 8+ Problem Details 规范(RFC 9457)
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
public class GlobalExceptionHandler : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext ctx,
Exception ex,
CancellationToken ct)
{
(int status, string title) = ex switch
{
ValidationException => (400, "Validation Error"),
NotFoundException => (404, "Not Found"),
UnauthorizedException => (401, "Unauthorized"),
_ => (500, "Internal Server Error")
};
ctx.Response.StatusCode = status;
await ctx.Response.WriteAsJsonAsync(new ProblemDetails
{
Status = status,
Title = title,
Detail = ex.Message
}, ct);
return true;
}
}
OpenAPI / Scalar(.NET 9 默认 UI)
.NET 9 内置了 OpenAPI 文档生成,并将 Scalar 作为默认 UI 替代 Swagger UI,界面更现代。
builder.Services.AddOpenApi(); // .NET 9 内置,无需 Swashbuckle
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // 生成 /openapi/v1.json
app.MapScalarApiReference(); // Scalar UI at /scalar/v1
}
// 丰富 OpenAPI 描述
app.MapGet("/api/users/{id}", async (int id, IUserService svc) => { /*...*/ })
.WithName("GetUser")
.WithSummary("获取用户详情")
.WithDescription("根据 ID 获取单个用户的完整信息")
.Produces<UserDto>(200)
.Produces(404)
.RequireAuthorization();
本章小结
Minimal API 适合小型/微服务场景,Controller 适合大型项目。中间件管道的顺序至关重要——认证必须在授权之前,路由必须在端点执行之前。DI 的三种生命周期中,DbContext 必须 Scoped,HttpClient 通过工厂管理。全局异常处理配合 Problem Details 规范,为 API 消费者提供一致的错误格式。.NET 9 内置 OpenAPI + Scalar,零配置即可获得交互式 API 文档。