Minimal API 轻量接口
大约 11 分钟约 3229 字
Minimal API 轻量接口
简介
Minimal API 是 .NET 6 引入的轻量级 Web API 开发方式。它用一行代码即可定义一个 API 端点,无需创建 Controller 类,极大简化了小型服务和微服务的开发。适用于轻量 API、Serverless、微服务网关后端等场景。
特点
基本用法
创建 Minimal API
/// <summary>
/// 最简单的 Minimal API — Program.cs 中直接定义
/// </summary>
var builder = WebApplication.CreateBuilder(args);
// 添加服务
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 一行定义 GET 端点
app.MapGet("/", () => "Hello World!");
// 带路由参数
app.MapGet("/users/{id}", (int id) => $"用户ID:{id}");
// POST 端点
app.MapPost("/users", (CreateUserRequest request) =>
{
return Results.Ok(new { Id = 1, request.Name });
});
app.Run();
public record CreateUserRequest(string Name, string Email);CRUD 完整示例
/// <summary>
/// 使用 Minimal API 实现完整 CRUD
/// </summary>
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IUserService, UserService>();
var app = builder.Build();
// GET — 获取所有
app.MapGet("/api/users", (IUserService service) =>
{
return Results.Ok(service.GetAll());
});
// GET — 按 ID 获取
app.MapGet("/api/users/{id:int}", (int id, IUserService service) =>
{
var user = service.GetById(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
});
// POST — 创建
app.MapPost("/api/users", (CreateUserRequest req, IUserService service) =>
{
var user = service.Create(req);
return Results.Created($"/api/users/{user.Id}", user);
});
// PUT — 更新
app.MapPut("/api/users/{id:int}", (int id, UpdateUserRequest req, IUserService service) =>
{
var success = service.Update(id, req);
return success ? Results.NoContent() : Results.NotFound();
});
// DELETE — 删除
app.MapDelete("/api/users/{id:int}", (int id, IUserService service) =>
{
var success = service.Delete(id);
return success ? Results.NoContent() : Results.NotFound();
});
app.Run();参数绑定
绑定来源
/// <summary>
/// Minimal API 的参数绑定 — 自动从请求中提取
/// </summary>
// 从路由绑定
app.MapGet("/orders/{orderId}/items/{itemId}", (int orderId, int itemId) =>
{
return $"订单 {orderId} 的商品 {itemId}";
});
// 从查询字符串绑定
app.MapGet("/search", (string keyword, int page = 1, int pageSize = 10) =>
{
return $"搜索:{keyword},第 {page} 页,每页 {pageSize} 条";
});
// 从请求体绑定(JSON)
app.MapPost("/products", (Product product) =>
{
return Results.Created($"/products/{product.Id}", product);
});
// 从 Header 绑定
app.MapGet("/profile", ([FromHeader(Name = "X-User-Id")] string userId) =>
{
return $"当前用户:{userId}";
});
// 从服务容器绑定(DI)
app.MapGet("/health", (IHealthCheckService health) =>
{
return Results.Ok(health.Check());
});
// 混合绑定
app.MapPut("/products/{id}", async (int id, Product product, IProductService service, CancellationToken ct) =>
{
return await service.UpdateAsync(id, product, ct);
});自定义绑定
/// <summary>
/// 自定义参数绑定逻辑
/// </summary>
public class TenantBinder : IBindableFromHttpContext<TenantInfo>
{
public static ValueTask<TenantInfo> BindAsync(HttpContext context, ParameterInfo parameter)
{
var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "default";
var tenant = new TenantInfo(tenantId, $"租户 {tenantId}");
return ValueTask.FromResult(tenant);
}
}
// 使用自定义绑定
app.MapGet("/dashboard", (TenantInfo tenant) =>
{
return $"当前租户:{tenant.Name}";
});结果类型
Results 返回
/// <summary>
/// Results — 类型安全的 HTTP 响应
/// </summary>
// 常用 Results
app.MapGet("/results", () =>
{
return Results.Ok(new { Message = "成功" }); // 200
});
app.MapGet("/created", () =>
{
return Results.Created("/items/1", new { Id = 1 }); // 201
});
app.MapGet("/nocontent", () =>
{
return Results.NoContent(); // 204
});
app.MapGet("/notfound", () =>
{
return Results.NotFound("资源不存在"); // 404
});
app.MapGet("/badrequest", () =>
{
return Results.BadRequest(new { Error = "参数错误" }); // 400
});
app.MapGet("/error", () =>
{
return Results.Problem("服务器内部错误"); // 500
});
// IResult 联合返回 — 编译时类型安全
app.MapGet("/users/{id}", (int id, IUserService service) =>
{
return service.GetById(id) switch
{
null => Results.NotFound(),
var user => Results.Ok(user)
};
})
.Produces<User>(200)
.Produces(404);中间件与过滤器
端点过滤器
/// <summary>
/// 端点过滤器 — 类似 MVC 的 ActionFilter
/// </summary>
// 验证过滤器
app.MapPost("/api/orders", (CreateOrderRequest req) =>
{
return Results.Ok(new { OrderId = Guid.NewGuid() });
})
.AddEndpointFilter(async (context, next) =>
{
var request = context.GetArgument<CreateOrderRequest>(0);
if (string.IsNullOrEmpty(request.ProductName))
return Results.BadRequest("商品名称不能为空");
if (request.Quantity <= 0)
return Results.BadRequest("数量必须大于0");
return await next(context);
});
// 日志过滤器
app.MapGet("/api/products", (IProductService service) =>
{
return service.GetAll();
})
.AddEndpointFilter<LoggingFilter>();
// 可复用的过滤器
public class LoggingFilter : IEndpointFilter
{
private readonly ILogger<LoggingFilter> _logger;
public LoggingFilter(ILogger<LoggingFilter> logger) => _logger = logger;
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
_logger.LogInformation("请求:{Method} {Path}", context.HttpContext.Request.Method, context.HttpContext.Request.Path);
var result = await next(context);
_logger.LogInformation("响应完成");
return result;
}
}安全与认证
JWT 认证
/// <summary>
/// Minimal API 中使用 JWT 认证
/// </summary>
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// 需要认证的端点
app.MapGet("/profile", (HttpContext http) =>
{
var userId = http.User.FindFirst("sub")?.Value;
return $"用户ID:{userId}";
}).RequireAuthorization();
// 允许匿名访问的端点
app.MapGet("/public", () => "公开数据").AllowAnonymous();
// 基于角色授权
app.MapDelete("/admin/users/{id}", (int id) =>
{
return Results.NoContent();
}).RequireAuthorization(policy => policy.RequireRole("Admin"));组织大型项目
分组路由
/// <summary>
/// 使用 MapGroup 组织路由
/// </summary>
var app = WebApplication.CreateBuilder(args).Build();
// 用户模块
var users = app.MapGroup("/api/users")
.WithTags("用户管理")
.WithOpenApi();
users.MapGet("/", (IUserService service) => service.GetAll());
users.MapGet("/{id}", (int id, IUserService service) => service.GetById(id));
users.MapPost("/", (CreateUserRequest req, IUserService service) => service.Create(req));
users.MapDelete("/{id}", (int id, IUserService service) => service.Delete(id));
// 订单模块
var orders = app.MapGroup("/api/orders")
.WithTags("订单管理")
.RequireAuthorization();
orders.MapGet("/", (IOrderService service) => service.GetAll());
orders.MapPost("/", (CreateOrderRequest req, IOrderService service) => service.Create(req));
// 版本控制
var v1 = app.MapGroup("/api/v1").WithTags("V1");
var v2 = app.MapGroup("/api/v2").WithTags("V2");
v1.MapGet("/products", () => "V1 产品列表");
v2.MapGet("/products", () => "V2 产品列表(增强版)");扩展方法封装
/// <summary>
/// 使用扩展方法将路由定义移出 Program.cs
/// </summary>
public static class UserEndpoints
{
public static void MapUserEndpoints(this WebApplication app)
{
var group = app.MapGroup("/api/users")
.WithTags("用户管理");
group.MapGet("/", GetAllUsers);
group.MapGet("/{id}", GetUserById);
group.MapPost("/", CreateUser);
group.MapPut("/{id}", UpdateUser);
group.MapDelete("/{id}", DeleteUser);
}
private static async Task<IResult> GetAllUsers(IUserService service)
=> Results.Ok(await service.GetAllAsync());
private static async Task<IResult> GetUserById(int id, IUserService service)
{
var user = await service.GetByIdAsync(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
}
private static async Task<IResult> CreateUser(CreateUserRequest req, IUserService service)
{
var user = await service.CreateAsync(req);
return Results.Created($"/api/users/{user.Id}", user);
}
private static async Task<IResult> UpdateUser(int id, UpdateUserRequest req, IUserService service)
{
return await service.UpdateAsync(id, req) ? Results.NoContent() : Results.NotFound();
}
private static async Task<IResult> DeleteUser(int id, IUserService service)
{
return await service.DeleteAsync(id) ? Results.NoContent() : Results.NotFound();
}
}
// Program.cs
app.MapUserEndpoints();验证与错误处理
FluentValidation 集成
/// <summary>
/// Minimal API 中使用 FluentValidation 进行请求验证
/// </summary>
using FluentValidation;
// 注册 FluentValidation
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
// 验证过滤器(可复用)
public class ValidationFilter<T> : IEndpointFilter
{
private readonly IValidator<T> _validator;
public ValidationFilter(IValidator<T> validator) => _validator = validator;
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var request = context.GetArgument<T>(0);
var result = await _validator.ValidateAsync(request);
if (!result.IsValid)
{
return Results.ValidationProblem(
result.Errors.GroupBy(e => e.PropertyName)
.ToDictionary(
g => g.Key,
g => g.Select(e => e.ErrorMessage).ToArray()
));
}
return await next(context);
}
}
// 请求验证器
public class CreateOrderRequestValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderRequestValidator()
{
RuleFor(x => x.ProductName)
.NotEmpty().WithMessage("商品名称不能为空")
.MaximumLength(200).WithMessage("商品名称最多200字");
RuleFor(x => x.Quantity)
.GreaterThan(0).WithMessage("数量必须大于0")
.LessThan(10000).WithMessage("单次最多订购10000件");
RuleFor(x => x.UnitPrice)
.GreaterThan(0).WithMessage("单价必须大于0")
.PrecisionScale(10, 2, true).WithMessage("单价格式不正确");
}
}
// 使用验证过滤器
app.MapPost("/api/orders", (CreateOrderRequest req, IOrderService service) =>
{
return Results.Ok(service.Create(req));
})
.AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();全局异常处理
/// <summary>
/// Minimal API 全局异常处理中间件
/// </summary>
app.Use(async (context, next) =>
{
try
{
await next(context);
}
catch (ValidationException ex)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsJsonAsync(new
{
Type = "ValidationError",
Errors = ex.Errors.Select(e => e.ErrorMessage)
});
}
catch (NotFoundException ex)
{
context.Response.StatusCode = 404;
await context.Response.WriteAsJsonAsync(new
{
Type = "NotFound",
Message = ex.Message
});
}
catch (BusinessException ex)
{
context.Response.StatusCode = 422;
await context.Response.WriteAsJsonAsync(new
{
Type = "BusinessError",
Message = ex.Message,
Code = ex.Code
});
}
catch (Exception ex)
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "未处理的异常: {Message}", ex.Message);
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new
{
Type = "InternalServerError",
Message = "服务器内部错误"
});
}
});
// 自定义异常类型
public class NotFoundException : Exception
{
public NotFoundException(string entity, object key)
: base($"未找到 {entity},标识:{key}") { }
}
public class BusinessException : Exception
{
public string Code { get; }
public BusinessException(string code, string message) : base(message) => Code = code;
}性能优化
输出缓存与响应压缩
/// <summary>
/// Minimal API 性能优化 — 缓存与压缩
/// </summary>
var builder = WebApplication.CreateBuilder(args);
// 输出缓存(.NET 7+)
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5)));
options.AddPolicy("Products", builder => builder
.Expire(TimeSpan.FromMinutes(10))
.Tag("products"));
});
// 响应压缩
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
});
var app = builder.Build();
app.UseResponseCompression();
app.UseOutputCache();
// 使用输出缓存
app.MapGet("/api/products", (IProductService service) =>
{
return service.GetAll();
}).CacheOutput("Products");
// 清除缓存
app.MapPost("/api/products", (CreateProductRequest req, IOutputCacheStore cache) =>
{
// 清除 products 相关缓存
cache.EvictByTagAsync("products", default);
return Results.Ok();
});请求限流与并发控制
/// <summary>
/// Rate Limiting — 限制请求频率
/// </summary>
builder.Services.AddRateLimiter(options =>
{
// 固定窗口限流
options.AddFixedWindowLimiter("fixed", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 10;
});
// 滑动窗口限流
options.AddSlidingWindowLimiter("sliding", opt =>
{
opt.PermitLimit = 60;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 6;
});
// 令牌桶限流(允许突发)
options.AddTokenBucketLimiter("token", opt =>
{
opt.TokenLimit = 100;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
opt.TokensPerPeriod = 20;
});
// 并发限流
options.AddConcurrencyLimiter("concurrency", opt =>
{
opt.PermitLimit = 10;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 5;
});
// 自定义限流响应
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.StatusCode = 429;
await context.HttpContext.Response.WriteAsJsonAsync(
new { Error = "请求过于频繁,请稍后重试" }, ct);
};
});
app.UseRateLimiter();
// 应用到端点
app.MapGet("/api/public-data", () => "公开数据")
.RequireRateLimiting("fixed");
app.MapGet("/api/heavy-compute", () => ComputeResult())
.RequireRateLimiting("concurrency");测试 Minimal API
集成测试
/// <summary>
/// Minimal API 集成测试 — 使用 WebApplicationFactory
/// </summary>
public class UserApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public UserApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 替换为测试用的内存数据库
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(opt =>
opt.UseInMemoryDatabase("TestDb"));
// 替换为模拟服务
services.RemoveAll<IUserService>();
services.AddScoped<IUserService, MockUserService>();
});
}).CreateClient();
}
[Fact]
public async Task GetAllUsers_ReturnsOk()
{
// Act
var response = await _client.GetAsync("/api/users");
// Assert
response.EnsureSuccessStatusCode();
var users = await response.Content.ReadFromJsonAsync<List<UserDto>>();
Assert.NotNull(users);
}
[Fact]
public async Task CreateUser_WithValidData_ReturnsCreated()
{
// Arrange
var request = new CreateUserRequest("测试用户", "test@example.com");
// Act
var response = await _client.PostAsJsonAsync("/api/users", request);
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var user = await response.Content.ReadFromJsonAsync<UserDto>();
Assert.Equal("测试用户", user?.Name);
}
[Fact]
public async Task CreateUser_WithInvalidData_ReturnsBadRequest()
{
var request = new CreateUserRequest("", "");
var response = await _client.PostAsJsonAsync("/api/users", request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
}OpenAPI 与文档生成
Swagger 深度配置
/// <summary>
/// Minimal API 的 OpenAPI 文档配置
/// </summary>
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "用户服务 API",
Version = "v1",
Description = "用户管理相关的 CRUD 接口"
});
// JWT 认证文档
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Description = "请输入 JWT Token"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
// 端点文档增强
app.MapGet("/api/users/{id}", (int id, IUserService service) =>
{
var user = service.GetById(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
})
.WithName("GetUserById")
.WithOpenApi(op =>
{
op.Summary = "根据 ID 获取用户";
op.Description = "返回指定用户的详细信息";
op.Parameters[0].Description = "用户唯一标识";
return op;
})
.Produces<UserDto>(200, "application/json")
.ProducesProblem(404, "application/json");Minimal API vs Controller API
| 特性 | Minimal API | Controller API |
|---|---|---|
| 代码量 | 极少 | 较多 |
| 学习成本 | 低 | 中等 |
| 适用场景 | 微服务、小 API | 大型项目 |
| 模型绑定 | 自动 + 自定义 | 特性标注 |
| 过滤器 | 端点过滤器 | ActionFilter |
| 路由 | Map 方法 | 属性路由 |
| Swagger | WithOpenApi() | 默认支持 |
| 复杂度 | 低 | 高 |
优点
缺点
总结
Minimal API 是 .NET 6+ 推荐的 API 开发方式。小型项目和微服务优先使用 Minimal API;大型复杂项目仍可使用 Controller API。两者可以共存,按需选择。核心原则:简单的事情简单做。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
- 框架能力的真正重点是它在请求链路中的位置和对上下游的影响。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
- 画清执行顺序、入参来源、失败返回和日志记录点。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
- 知道 API 名称,却不知道它应该放在请求链路的哪个位置。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
- 继续补齐协议选型、网关治理、端点可观测性和契约演进策略。
适用场景
- 当你准备把《Minimal API 轻量接口》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《Minimal API 轻量接口》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Minimal API 轻量接口》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Minimal API 轻量接口》最大的收益和代价分别是什么?
