news 2026/9/4 6:58:48

ASP.NET Core 10 Minimal APIs 实战:轻量 API 开发与性能观察

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASP.NET Core 10 Minimal APIs 实战:轻量 API 开发与性能观察

开始之前先给结论:Minimal APIs 不是玩具,也不是只能写 demo 的边角功能。在 ASP.NET Core 10 这一代,它已经是能支撑微服务、工具型 API、Webhook、后台任务入口的正式推荐方案。相比传统 Controller API,Minimal APIs 的代码更短、启动更快、路由表达更直接,而且从项目创建到 OpenAPI 文档生成,整条链路都能在 10 分钟内跑通。这篇文章就带着你从头跑一遍:安装 .NET 10 SDK、创建项目、编写 GET/POST/PUT/DELETE 接口、开启 OpenAPI 文档、做批量导入接口,再用 curl 和 Python 调用验证。

如果你之前只写过传统的 ASP.NET Core Controller,Minimal APIs 可以看成同一种后台能力的另一套写法。它没有推翻依赖注入、配置系统、中间件管线这些 ASP.NET Core 底层设计,而是取消掉了 Controller、Action、Attribute 带来的大量样板代码,把“HTTP 路径”和“C# 方法”直接对应起来。这种写法对小型服务非常友好:一个 Program.cs 就是完整应用,少了一层目录结构,少了类之间的跳转,阅读和维护成本都低不少。

这次我们不会停在“能启动”层面,还会把几个经常被忽略的问题一起讲清楚:Minimal API 的参数绑定规则是什么,复杂类型从 JSON Body 来,简单类型从路由或查询字符串来,这一点直接影响接口能不能被正确调用。还有 OpenAPI 文档怎么生成、批量写入接口怎么做数量限制、服务启动后内存和线程状态怎么观察、发布到生产环境时该用普通发布还是 Native AOT。这些都是实际开发中会遇到的真实问题。

适合看这篇文章的读者有三类:第一是刚入门 .NET 后端,想找一套轻量 API 开发模式的 C# 开发者;第二是在维护老 Controller 项目,考虑把新增接口改成 Minimal API 的技术负责人;第三是做小工具、内部服务、脚本调用入口,想少写大量装饰器代码的开发者。下面直接进入正题。

1. Minimal APIs 核心能力速览

能力项说明
所属框架ASP.NET Core 10,.NET 10 SDK 内置
API 模式Minimal APIs,自 .NET 6 引入,在 .NET 10 中已成熟
项目形态单个 Program.cs 或按模块拆分,没有 Controller 目录
启动方式dotnet run,也可以在 Visual Studio / VS Code / Rider 中直接调试
路由能力MapGetMapPostMapPutMapDeleteMapGroup,支持路由参数约束
依赖注入路由处理器参数直接注入服务,不需要构造函数
OpenAPI 支持通过AddOpenApi()MapOpenApi()生成 JSON 文档
批量任务没有内置队列,但可以快速实现批量导入接口,或结合 BackgroundService 消费任务
混合使用同一个应用中可同时存在 Minimal API 与 Controller API
适用平台Windows / Linux / macOS,支持 Docker 部署
典型场景微服务、Webhook、小工具 API、后台管理系统接口、演示项目

说明一点:上表中的“批量任务”和“支持 API”指的是框架能力边界。Minimal APIs 属于 Web 框架,不内置消息队列,任务队列通常需要你自己定义并发模型,或者引入 Channel、Hangfire、Quartz 这类组件。后面会给出一个可运行的批量导入接口示例,你可以在此基础上扩展成真正的后台队列。

2. Minimal APIs 使用场景与适用边界

2.1 什么场景适合 Minimal API

Minimal API 最适合的是“接口数量有限,但希望接口足够清晰”的应用。典型场景包括:前后端分离项目中的小模块 API、定时任务管理系统中的任务提交接口、Webhook 接收接口、内部运维工具接口,以及以 CRUD 为主的简单业务系统。这类项目如果用 Controller,会感觉每个 Action 都是一层包装:创建控制器类、声明路由、声明 HttpGet/HttpPost、再用构造函数注入服务,真正业务代码只有几行,而周边样板有几十行。

模块化方面,Minimal API 并不弱。ASP.NET Core 8 之后已经加入了MapGroup,可以把一组路由组织成组,统一配置前缀、标签、过滤器。例如:

var todoApi = app.MapGroup("/api/items") .WithTags("TodoItems"); todoApi.MapGet("/", (ItemRepository repo) => ...); todoApi.MapPost("/", (TodoCreateRequest input, ItemRepository repo) => ...);

这样代码结构仍然能按业务拆开,放在不同的静态类或扩展方法中,不会因为用了 Minimal API 就让所有接口挤在同一个文件里。

2.2 什么场景不建议用 Minimal API

不建议用 Minimal API 的情况也要说清楚。如果你维护的是一个大型 ERP 或中台系统,接口数量几百上千,并且团队已经养成了 Controller + Service + Repository 的分层习惯,那么整个团队继续使用 Controller 可能是更好的选择。原因不是 Minimal API 能力不够,而是团队协作时,统一模式往往比单接口代码量更重要。Controller 路由和授权特性已经形成了一套强约定,新人迁移成本低,代码检索也方便。

另外,如果你需要大量使用继承、拦截器、统一模型绑定逻辑等强框架特性,Controller 基类提供的封装能力仍然有优势。Minimal API 不是“取代” Controller,而是“另一种更轻的选项”。工程选型不应该只看 Demo 有多简洁,还要看项目生命周期里的维护成本。

2.3 使用边界与合规提醒

从技术上,Minimal API 可以构建公开的数据接口,也可以构建处理用户信息、上传文件、人脸图片等服务。涉及处理个人数据、用户文件或第三方内容时,必须在接口设计阶段考虑授权、日志、访问限流和隐私保护要求。不要在公网接口上不做鉴权就开放写入能力,不要用示例中的内存仓储处理生产数据,也不要对未授权的版权内容、他人肖像、敏感文件做采集或再分发。示例代码请在本地开发环境验证。

3. ASP.NET Core 10 本地开发环境准备

3.1 安装 .NET 10 SDK

在开始写代码之前,先确认本机已经安装了 .NET 10 SDK。这里只依赖一个官方运行时,不需要额外安装数据库和第三方依赖。Linux、macOS、Windows 的安装方式不同,最简单的方式是打开终端,用常见包管理器安装。

Windows 上可以用 winget 搜索并安装:

winget search "Microsoft.DotNet.SDK"

从搜索列表中选择对应 .NET 10 的 SDK 包进行安装。macOS 上可以使用 brew:

brew install dotnet-sdk-10

Linux 上建议使用微软官方 Linux 软件源,然后通过 apt 或 dnf 安装。如果你不想折腾包管理器,也可以直接去 Microsoft 官网下载对应系统的 SDK 安装包。安装完成后重启终端,让环境变量生效。

3.2 验证环境是否可用

打开终端,执行下面两条命令,确认 SDK 安装成功:

dotnet --version dotnet --list-sdks

dotnet --version会输出当前默认的 SDK 版本,例如 10.0.100 或类似版本号。dotnet --list-sdks会列出本机安装的全部 SDK。如果这里看不到任何 .NET 10 版本,后面的项目创建就会失败,需要先解决 SDK 安装问题再继续。

接下来再看一下项目模板是否正常。执行:

dotnet new list

输出列表中应该能看到 “ASP.NET Core Web API” 和 “ASP.NET Core Empty” 两个模板,它们都可以用来创建 Minimal API 项目。

3.3 准备编辑器与可选工具

编辑器方面,Windows 下可以用 Visual Studio 最新稳定版,装好ASP.NET 和 Web 开发工作负载;跨平台开发更推荐 Visual Studio Code 搭配 C# Dev Kit 扩展,或者使用 JetBrains Rider。这些工具都支持直接打开项目运行调试。

如果需要观察接口性能和资源占用,可以额外安装 dotnet-counters 工具:

dotnet tool install --global dotnet-counters

这个工具后面在第 7 章会用到,主要是用来查看进程的 CPU、内存、线程池和 GC 状态,不需要写任何额外代码。

4. 创建 Minimal API 项目与启动服务

4.1 创建项目

我习惯用 Empty 模板创建项目,因为这样生成的代码最干净,没有大量多余的模板注释。打开终端,执行:

dotnet new web -n Todo.MinimalApi cd Todo.MinimalApi

执行完后目录里会包含一个 Todo.MinimalApi.csproj 和 Program.cs。此时项目已经是一个能运行的 Minimal API 应用,但默认只有一个根路由。我们先添加 OpenAPI 支持:

dotnet add package Microsoft.AspNetCore.OpenApi

这个包用于生成 OpenAPI 文档。如果网络环境访问 NuGet 比较慢,可以确认一下 NuGet 源配置;国内开发者也可以把 nuget.org 源和镜像源的速度做一次对比,选择更快的源。

4.2 编写完整的示例子代码

把默认的 Program.cs 替换成下面的内容。这个示例包含了一个内存版 Todo 仓储,以及完整的增删改查接口:

var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); builder.Services.AddSingleton<ItemRepository>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.MapGet("/", () => Results.Ok(new { service = "ASP.NET Core 10 Minimal API", time = DateTimeOffset.Now })); app.MapGet("/api/items", (ItemRepository repo) => { var items = repo.GetAll(); return Results.Ok(items); }); app.MapGet("/api/items/{id:int}", (int id, ItemRepository repo) => { var item = repo.GetById(id); return item is null ? Results.NotFound() : Results.Ok(item); }); app.MapPost("/api/items", (TodoCreateRequest input, ItemRepository repo) => { if (string.IsNullOrWhiteSpace(input.Title)) { return Results.ValidationProblem(new Dictionary<string, string[]> { ["title"] = new[] { "title 不能为空" } }); } var created = repo.Add(new TodoItem { Title = input.Title, IsCompleted = input.IsCompleted }); return Results.Created($"/api/items/{created.Id}", created); }); app.MapPut("/api/items/{id:int}", (int id, TodoUpdateRequest input, ItemRepository repo) => { var updated = repo.Update(id, new TodoItem { Id = id, Title = input.Title, IsCompleted = input.IsCompleted }); return updated is null ? Results.NotFound() : Results.Ok(updated); }); app.MapDelete("/api/items/{id:int}", (int id, ItemRepository repo) => { return repo.Delete(id) ? Results.NoContent() : Results.NotFound(); }); app.Run(); public class TodoItem { public int Id { get; set; } public string Title { get; set; } = string.Empty; public bool IsCompleted { get; set; } } public class TodoCreateRequest { public string Title { get; set; } = string.Empty; public bool IsCompleted { get; set; } } public class TodoUpdateRequest { public string Title { get; set; } = string.Empty; public bool IsCompleted { get; set; } } public class ItemRepository { private readonly Dictionary<int, TodoItem> _items = new(); private int _nextId = 1; public List<TodoItem> GetAll() => _items.Values.OrderBy(item => item.Id).ToList(); public TodoItem? GetById(int id) => _items.TryGetValue(id, out var item) ? item : null; public TodoItem Add(TodoItem item) { item.Id = _nextId++; _items[item.Id] = item; return item; } public TodoItem? Update(int id, TodoItem item) { if (!_items.ContainsKey(id)) { return null; } item.Id = id; _items[id] = item; return item; } public bool Delete(int id) => _items.Remove(id); }

这段代码的重点是让你感受 Minimal API 的核心写法:MapGetMapPostMapPutMapDelete后面的第一个参数是路由模板,第二个参数是路由处理器。路由处理器里需要的服务会通过参数自动注入,比如ItemRepository repo就是从依赖注入容器里拿到的单例服务。

4.3 启动并验证端口

启动项目,执行:

dotnet run

如果一切正常,终端会输出类似 “Now listening on: http://localhost:5xxx” 的日志。模板可能使用随机端口,所以不要死记某个固定端口,以你自己的终端输出为准。如果你想让端口固定下来,可以把启动命令改成:

dotnet run --urls http://localhost:5167

然后打开浏览器访问http://localhost:5167/,如果看到 JSON 输出,说明服务已经正常启动。终端里出现监听地址后,先不要关闭窗口,因为后续的接口测试都依赖这个服务。

5. Minimal API 功能测试与效果验证

5.1 健康检查路由

先测试根路由,判断服务是否还在运行:

curl http://localhost:5167/

预期输出类似:

{"service":"ASP.NET Core 10 Minimal API","time":"2026-01-01T10:00:00+08:00"}

只要能拿到 JSON,就说明 .NET 10 运行时、SDK 环境和项目代码都没有问题。如果你看到的是“Connection refused”,第一件事是确认服务进程是否还活着,第二件事是检查端口是否写对。

5.2 创建资源并查询资源

接下来向/api/items发送 POST 请求,创建一个新条目:

curl -i -X POST http://localhost:5167/api/items \ -H "Content-Type: application/json" \ -d '{"title":"写一篇 CSDN 技术博客","isCompleted":false}'

注意,Minimal API 默认使用 JSON 大小写不敏感的属性绑定方式,所以请求体里写titleTitle都能绑定到TodoCreateRequest.Title。正常情况下会返回201 Created,响应头里有Location,响应体类似:

{ "id": 1, "title": "写一篇 CSDN 技术博客", "isCompleted": false }

现在用 GET 请求验证数据已经存进内存仓储:

curl http://localhost:5167/api/items

如果刚才创建成功,返回列表里应该能看到 id 为 1 的那条记录。继续测试单条查询:

curl http://localhost:5167/api/items/1 curl -i http://localhost:5167/api/items/999

第一条返回刚才创建的对象,第二条由于 id 不存在,预期返回 404。这种简单验证能帮你确认路由约束{id:int}是否生效,以及Results.NotFound()是否按预期工作。

5.3 更新与删除资源

更新资源:

curl -i -X PUT http://localhost:5167/api/items/1 \ -H "Content-Type: application/json" \ -d '{"title":"写一篇 CSDN 技术博客并发布","isCompleted":true}'

预期返回 200,并且响应体中的isCompleted变为 true。然后删除资源:

curl -i -X DELETE http://localhost:5167/api/items/1

预期返回 204 No Content。再查一次单条资源,应该变成 404。

5.4 参数校验与错误返回

Minimal API 没有内置 FluentValidation,但可以像示例中那样自己写一个if判断。比如 POST 一个空标题:

curl -i -X POST http://localhost:5167/api/items \ -H "Content-Type: application/json" \ -d '{"title":"","isCompleted":false}'

预期返回 400,并且响应体里带有标准问题详情格式,其中errors.title会包含我们写的“title 不能为空”。这个细节提示了 Minimal API 的出错返回是可以被前端统一解析的,不要只返回裸字符串。

6. Minimal API 的 OpenAPI 文档与批量任务接口

6.1 启动 OpenAPI JSON 文档

在 Program.cs 中,我们已经调用了builder.Services.AddOpenApi()app.MapOpenApi()。服务运行时,直接访问:

curl http://localhost:5167/openapi/v1.json

如果能看到一份 JSON 文档,说明 OpenAPI 已经生效。这里生产的是 OpenAPI 3.0/3.1 格式的接口描述,它本身不是 UI 页面。想要可视化调试界面,可以把这份 JSON 地址接到 Scalar、Swagger UI 或 Postman 里。接口每次发生变化时,OpenAPI 文档会同步体现在这个 JSON 里,不需要额外维护接口文档。

6.2 实现批量导入接口

现在在 Program.cs 中添加一个新的批量导入路由。它接收一个数组,限制单次最大 100 条,并返回创建后的完整数据。在app.Run()之前加入如下代码:

app.MapPost("/api/items/batch", (List<TodoCreateRequest> inputs, ItemRepository repo) => { if (inputs.Count > 100) { return Results.BadRequest(new { error = "单次批量最多 100 条" }); } var created = new List<TodoItem>(inputs.Count); foreach (var input in inputs) { if (string.IsNullOrWhiteSpace(input.Title)) { return Results.ValidationProblem(new Dictionary<string, string[]> { ["title"] = new[] { "title 不能为空" } }); } created.Add(repo.Add(new TodoItem { Title = input.Title, IsCompleted = input.IsCompleted })); } return Results.Created("/api/items", created); });

这个接口说明了几个生产注意事项:

  1. 批量操作必须加数量上限,避免一次请求打爆进程内存或数据库连接。
  2. 批量接口不是一定比多个单条请求更快,真正瓶颈往往在数据库写入和事务处理。
  3. 如果中途某一条数据校验失败,通常建议快速失败并返回明确错误,而不是写一半再回滚。

测试批量接口:

curl -i -X POST http://localhost:5167/api/items/batch \ -H "Content-Type: application/json" \ -d '[{"title":"任务1"},{"title":"任务2"},{"title":"任务3"}]'

预期响应里包含三个创建后的对象,状态码为 201。可以在后续开发中把它扩展为异步任务队列模式:接口先返回 202 Accepted 和一个 jobId,后台再用BackgroundService消费队列中的任务,这样就能把耗时的批量处理从 HTTP 请求中剥离出去。

6.3 用 Python requests 调用接口

Minimal API 本质就是一个普通 HTTP 服务,所以任何支持 HTTP 的客户端都可以调用。这里给一个 Python 调用示例,方便你在脚本中快速批量写入:

import requests base_url = "http://localhost:5167" # 查询现有条目 resp = requests.get(f"{base_url}/api/items", timeout=10) print("GET /api/items", resp.status_code, resp.json()) # 单条创建 resp = requests.post( f"{base_url}/api/items", json={"title": "来自 Python 的任务", "isCompleted": False}, timeout=10, ) print("POST /api/items", resp.status_code, resp.json()) # 批量创建 payload = [ {"title": "批量任务-1"}, {"title": "批量任务-2"}, ] resp = requests.post( f"{base_url}/api/items/batch", json=payload, timeout=30, ) print("POST /api/items/batch", resp.status_code, resp.json())

写脚本时注意设置 timeout,不要用默认的无超时方式调用公网接口。批量任务耗时可能比较长,可以把 timeout 调大,但更好的做法是使用异步任务接口,让客户端轮询任务状态。

7. 资源占用与性能观察

7.1 开发模式下观察服务状态

Minimal API 的优势之一是没有 Controller 层的模板代码,因此进程启动速度通常更快,内存占比更稳定。但具体内存占用、

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 6:58:27

分布式电源接入配电网的谐波与电压波动仿真分析及治理策略

简介&#xff1a;本资源聚焦分布式电源接入对配电网的影响分析&#xff0c;面向电力系统专业本科生、研究生及从事新能源并网研究的工程师&#xff0c;重点解决谐波污染、电压波动与总谐波畸变率&#xff08;THD&#xff09;升高三大核心问题。压缩包共17个文件&#xff0c;含1…

作者头像 李华
网站建设 2026/9/4 6:56:54

从宏大概念到可执行项目:以空间站模拟为例的工程化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 6:55:59

基于SpringBoot的游戏论坛的设计与实现(源码+lw+部署文档+讲解等)

联系博主 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 …

作者头像 李华
网站建设 2026/9/4 6:55:47

MiniMax H3 ComfyUI 加速实战:V3 LoRA 低步数采样优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 6:53:09

构建公共历史资源数据库:技术架构、数据治理与开源实践

这次我们来看一个关于公共历史资源数据库的构想。这个想法源于一个核心观点&#xff1a;现行的知识产权制度&#xff0c;作为源自西方的法律体系&#xff0c;在保护中国历史悠久、形态多样的公共历史资源时&#xff0c;常常显得力不从心。无论是流传千年的民间故事、传统技艺&a…

作者头像 李华
网站建设 2026/9/4 6:50:49

基于 SpringBoot的连锁门店智能调配信息管理系统毕业设计项目源码

联系博主 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 …

作者头像 李华