创建第一个 Minimal API
初始化 Minimal API 项目,定义路由处理程序,并以最少的样板代码返回类型化结果。
创建第一个 Minimal API 是 CoddyKit 上的免费 C# Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
什么是最小 API
最小 API 于 .NET 6 引入,可以用极少的样板代码构建 HTTP 端点——无需控制器和操作特性,只需在 Program.cs 中直接定义路由处理程序。它们非常适合微服务和轻量级 API。
最简单的最小 API
只需几行代码即可完成一个 HTTP API。MapGet、MapPost、MapPut 和 MapDelete 方法分别为每种 HTTP 方法定义路由处理程序。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello, Minimal API!");
app.MapGet("/ping", () => Results.Ok(new { status = "pong" }));
app.Run();
// That's it — no Startup.cs, no controllers使用类型化结果返回正确的 HTTP 响应
使用 Results 或 TypedResults 返回正确的 HTTP 状态码和内容类型。对于 OpenAPI 架构推断,优先使用 TypedResults。
app.MapGet("/products/{id}", async (int id, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
return product is null
? Results.NotFound()
: Results.Ok(product);
});
app.MapPost("/products", async (Product product, AppDbContext db) =>
{
db.Products.Add(product);
await db.SaveChangesAsync();
return Results.Created($"/products/{product.Id}", product);
});路由参数与查询字符串
路由参数从 URL 路径中获取,查询字符串参数会自动从查询中绑定,请求正文则从 JSON 反序列化得到。
// Route param {id} + query param ?includeDeleted
app.MapGet("/orders/{id}", async (
int id,
bool includeDeleted = false,
AppDbContext db) =>
{
var query = db.Orders.AsQueryable();
if (!includeDeleted) query = query.Where(o => !o.IsDeleted);
var order = await query.FirstOrDefaultAsync(o => o.Id == id);
return order is null ? Results.NotFound() : Results.Ok(order);
});路由处理程序中的依赖注入
在 DI 容器中注册的服务可以直接注入为路由处理程序参数。框架会自动解析这些服务。
builder.Services.AddScoped<ProductService>();
app.MapGet("/products", async (ProductService svc) =>
{
var products = await svc.GetAllAsync();
return Results.Ok(products);
});
app.MapDelete("/products/{id}", async (int id, ProductService svc) =>
{
var deleted = await svc.DeleteAsync(id);
return deleted ? Results.NoContent() : Results.NotFound();
});请求正文绑定
与已注册服务匹配的参数会被注入;其他所有参数都会从请求正文中绑定(默认使用 JSON)。如有需要,请显式使用 [FromBody]。
record CreateProductRequest(string Name, decimal Price, int Stock);
app.MapPost("/products", async (
CreateProductRequest req,
ProductService svc) =>
{
var product = await svc.CreateAsync(req.Name, req.Price, req.Stock);
return TypedResults.Created($"/products/{product.Id}", product);
});返回不同的状态码
Results 为所有常见的 HTTP 响应提供工厂方法。请使用这些方法构建语义正确的 REST API。
app.MapPut("/products/{id}", async (int id, Product update, AppDbContext db) =>
{
var existing = await db.Products.FindAsync(id);
if (existing is null) return Results.NotFound();
existing.Name = update.Name;
existing.Price = update.Price;
await db.SaveChangesAsync();
return Results.Ok(existing);
});
// Other useful Results:
// Results.BadRequest("message")
// Results.Conflict()
// Results.UnprocessableEntity(errors)
// Results.Accepted()使用 WithName 和 WithTags 添加元数据
为端点附加元数据,以改善文档和路由。使用 WithName、WithTags 和 WithSummary 生成结构清晰的 OpenAPI 输出。
app.MapGet("/products/{id}", GetProduct)
.WithName("GetProductById")
.WithTags("Products")
.WithSummary("Retrieves a product by its ID")
.Produces<Product>()
.Produces(404);
static async Task<IResult> GetProduct(int id, AppDbContext db)
{
var p = await db.Products.FindAsync(id);
return p is null ? Results.NotFound() : Results.Ok(p);
}最小 API 中的授权
应用 RequireAuthorization() 保护端点,或使用 AllowAnonymous() 选择免授权。策略的工作方式与控制器中相同。
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/profile", (ClaimsPrincipal user) =>
Results.Ok(user.Identity!.Name))
.RequireAuthorization();
app.MapGet("/public", () => "No auth needed")
.AllowAnonymous();使用静态方法组织代码
对于较大的 API,可将路由处理程序移至静态方法或扩展方法中,以保持 Program.cs 整洁且便于浏览。
// Extension method groups endpoints by feature
public static class ProductEndpoints
{
public static void MapProductEndpoints(this WebApplication app)
{
app.MapGet("/products", GetAll);
app.MapGet("/products/{id}", GetById);
app.MapPost("/products", Create);
}
private static async Task<IResult> GetAll(AppDbContext db)
=> Results.Ok(await db.Products.AsNoTracking().ToListAsync());
// ... other handlers
}
// In Program.cs:
app.MapProductEndpoints();真实案例:完整的 CRUD 最小 API
一个针对 Todo 资源的完整最小 CRUD API——简洁、可测试,并且可用于生产环境。
app.MapGet("/todos", async (AppDbContext db) =>
Results.Ok(await db.Todos.AsNoTracking().ToListAsync()));
app.MapGet("/todos/{id}", async (int id, AppDbContext db) =>
{
var todo = await db.Todos.FindAsync(id);
return todo is null ? Results.NotFound() : Results.Ok(todo);
});
app.MapPost("/todos", async (Todo todo, AppDbContext db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todos/{todo.Id}", todo);
});
app.MapDelete("/todos/{id}", async (int id, AppDbContext db) =>
{
int n = await db.Todos.Where(t => t.Id == id).ExecuteDeleteAsync();
return n > 0 ? Results.NoContent() : Results.NotFound();
});快速检查
最小 API 的路由处理程序如何接收已注册的 DI 服务?
回顾:创建您的第一个最小 API
要点:
- MapGet/Post/Put/Delete 可直接在 Program.cs 中定义路由处理程序
- 使用
Results/TypedResults返回正确的 HTTP 状态码 - 路由、查询、正文和 DI 参数都会自动绑定
- 使用
RequireAuthorization()和AllowAnonymous()进行身份验证 - 使用扩展方法或路由组组织大型 API
用 AI 导师学习 C# — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 93
- 课程
- 346
常见问题解答
「创建第一个 Minimal API」课时是免费的吗?
是的 — 「创建第一个 Minimal API」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「创建第一个 Minimal API」这节课中我会学到什么?
初始化 Minimal API 项目,定义路由处理程序,并以最少的样板代码返回类型化结果。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「创建第一个 Minimal API」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 创建第一个 Minimal API
- 路由组、参数与验证
- Minimal API 中间件与筛选器
- OpenAPI、版本控制与部署