4.3 Controller 与 Minimal API 对比
ASP.NET Core 同时支持 Controller 和 Minimal API。两者都能构建生产级 Web API,差异不在“谁更高级”,而在组织方式、约定数量、可扩展点和团队协作成本。
学习目标
- 能解释 Controller 与 Minimal API 的路由、绑定和返回结果差异。
- 能根据项目规模、团队习惯和接口复杂度选择实现方式。
- 能把同一个 Todo 接口分别写成 Minimal API 和 Controller。
- 能避免把两种风格混在一起导致结构混乱。
应用场景
- 小型服务、内部工具或教学项目选择 Minimal API 快速起步。
- 中大型业务系统使用 Controller 组织资源、过滤器和版本演进。
- 在遗留 MVC 项目中继续维护 Controller,同时新增少量轻量接口。
- 团队制定后端代码规范时统一接口组织方式。
核心概念
| 对比点 | Minimal API | Controller |
|---|---|---|
| 路由定义 | 在 Program.cs 或扩展方法中使用 MapGet、MapPost | 通过 [Route]、[HttpGet] 等特性定义 |
| 代码组织 | 路由处理器函数更直接,需要主动拆分 | 类和方法天然按资源分组 |
| 返回结果 | 常用 Results、TypedResults、IResult | 常用 ActionResult<T>、IActionResult |
| 横切逻辑 | 使用中间件、Endpoint Filter、扩展方法 | 使用中间件、Action Filter、Authorization Filter |
| 学习曲线 | 入口少,上手快 | 约定更多,但大型项目更规整 |
案例:同一个查询接口的两种写法
业务要求:根据任务 ID 查询 Todo,找不到返回 404,找到返回 DTO。
Minimal API 写法
app.MapGet("/todos/{id:int}", async (
int id,
ITodoQueryService todoService,
CancellationToken cancellationToken) =>
{
var todo = await todoService.GetByIdAsync(id, cancellationToken);
return todo is null ? Results.NotFound() : Results.Ok(todo);
})
.WithName("GetTodoById")
.WithOpenApi();
Minimal API 的优势是路径、参数、服务注入和返回结果都在一处,非常适合小接口和示例代码。缺点是接口变多后,必须主动把路由注册拆到扩展方法或模块类里。
Controller 写法
[ApiController]
[Route("api/todos")]
public sealed class TodosController(ITodoQueryService todoService) : ControllerBase
{
[HttpGet("{id:int}", Name = "GetTodoById")]
[ProducesResponseType<TodoDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<TodoDto>> GetById(
int id,
CancellationToken cancellationToken)
{
var todo = await todoService.GetByIdAsync(id, cancellationToken);
return todo is null ? NotFound() : Ok(todo);
}
}
Controller 的优势是资源边界清晰,特性、过滤器和返回类型约定成熟。对于接口很多、权限规则复杂、团队成员较多的项目,它通常更容易统一风格。
示例:Controller 项目配置
如果项目使用 Controller,需要注册 MVC Controller 服务,并把 Controller 映射到请求管线。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapControllers();
app.Run();
Minimal API 和 Controller 可以共存,但应有明确边界。例如:核心业务 API 全部用 Controller,健康检查和内部探针用 Minimal API;不要同一个资源一半写在 Controller,一半散落在 Program.cs。
选型建议
| 情况 | 推荐 |
|---|---|
| 只有少量接口、原型或教学示例 | Minimal API |
| 接口按资源分组,数量持续增加 | Controller |
| 需要大量过滤器、模型约定和版本管理 | Controller |
| 构建独立微服务或轻量后台接口 | Minimal API 或模块化 Minimal API |
| 团队已有统一 Controller 规范 | 继续使用 Controller |
重点难点
- Minimal API 不等于所有代码都写在
Program.cs,复杂项目仍要拆分注册方法和业务服务。 - Controller 不等于传统 MVC 页面;Web API Controller 通常继承
ControllerBase,不返回 View。 - 两种写法都应该返回 DTO,不要直接暴露 EF Core 实体。
- 认证授权、中间件、依赖注入、配置和日志是共享机制,不依赖某一种路由风格。
- 团队项目中最重要的是一致性,同一资源最好只采用一种接口组织方式。
常见误区
| 误区 | 推荐做法 |
|---|---|
| 认为 Minimal API 只能写玩具项目 | 小而清晰的服务可以使用模块化 Minimal API |
| 认为 Controller 已经过时 | 大型业务 API 仍然非常适合 Controller |
| 两种风格随意混用 | 给共存场景设定明确边界 |
| 路由处理器直接写数据库细节 | 注入服务层,保持入口代码薄 |
| 只按个人喜好选型 | 结合接口规模、团队规范和扩展点选择 |
练习
- 把一个
MapPost("/todos")改写成TodosController.Create。 - 给 Controller 示例增加
POST /api/todos,返回 201 和资源地址。 - 把 5 个 Minimal API 路由移动到
MapTodoEndpoints扩展方法中。 - 写一份团队规范:哪些接口用 Controller,哪些接口允许用 Minimal API。
延伸阅读
- 本手册:Minimal API 入门
- 本手册:模型绑定与输入校验
- 本手册:Problem Details 与 OpenAPI