【免费下载链接】practical-aspnetcore
Practical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.
本篇技术指南以 practical-aspnetcore 仓库中的 map-post-2 示例 为核心,完整演示如何在 ASP.NET Core Minimal API 中使用 MapPost 注册一个处理 POST 请求的端点,并通过 [FromBody] 属性完成 JSON 请求体的模型绑定,同时将处理逻辑封装在静态方法中。读完本文,你将掌握方法组(method group)式端点注册、显式 JSON 绑定、record 类型与 Results.Json 返回响应的完整套路,并能直接照抄源码运行验证。
示例定位:map-post-2 在仓库中的角色
该示例位于 projects/minimal-api/map-post-2/,是仓库 minimal-api 目录 下"Minimal Routing"系列中的一个最小可运行项目。根据仓库总 README 的归类说明,它属于"MapPost with JSON model binder - 2",专门演示 使用静态方法(static method) 来实现 JSON 模型绑定——与同目录下的 map-post 示例(使用顶层局部函数)形成对照。
项目只包含三个文件,结构非常干净:
- Program.cs —— 全部业务逻辑(单文件 Minimal API)
- map-post-2.csproj —— 项目文件
- README.md —— 说明文档
其中 map-post-2.csproj 显示目标框架为 net10.0,并启用了 ImplicitUsings;仓库根目录的 global.json 指定的 SDK 版本为 10.0.0。因此该示例面向 .NET 10,运行前请确保本机安装了对应的 .NET SDK。
完整源码逐段拆解
整个示例的核心代码只有 45 行,全部位于 Program.cs,我们按功能块逐一分析。
1. 创建应用与注册 POST 端点
using System;
using Microsoft.AspNetCore.Mvc;
var app = WebApplication.Create();
app.MapPost("/greet", MyApi.Greet);
WebApplication.Create()是 Minimal API 的极简托管入口,不需要IHostBuilder或Startup类;MapPost("/greet", MyApi.Greet)注册了一个 HTTP POST 端点,路由模板为/greet;- 关键点在于第二参数传入的是 方法组(method group)
MyApi.Greet,即一个静态类上的静态方法引用,而不是内联 lambda 或局部函数。这就是本示例区别于 map-post(局部函数IResult Greet(...)直接传入MapPost)的核心差异。
2. 静态方法 + [FromBody] JSON 模型绑定
public record Greeting(string Message);
public static class MyApi
{
public static IResult Greet([FromBody] Greeting greet) => Results.Json(new { greet.Message });
}
public record Greeting(string Message):使用record定义一个位置参数(positional)数据传输对象,Message属性与请求 JSON 中的message字段对应;public static class MyApi:把端点处理逻辑封装为静态类中的静态方法,便于将 API 处理器与顶层语句分离,也方便单元测试直接调用(仓库中的 typed-results-1 示例 就是这种"处理器 + 测试"组织的例证,其Hello.GetGreeting静态方法可被 xUnit 的[Fact]直接断言);[FromBody] Greeting greet:显式声明参数从请求体(request body)绑定。Minimal API 会对带有[FromBody]的复杂类型参数,按Content-Type: application/json(或application/*+json)解析请求体,并使用内置的System.Text.Json反序列化为Greeting实例。若请求体缺失或 JSON 无法反序列化,框架会返回 400 错误;Results.Json(new { greet.Message }):返回 JSON 格式的响应。这里使用匿名对象(anonymous type)包装Message属性,输出为{"message":"..."}。Results.Json与 MVC 中的JsonResult作用类似,是 Minimal API 内置的IResult实现之一。
3. 根路径返回演示页面
app.MapGet("/", async context =>
{
string page = @"<!doctype html><html><body>
<div id=""response""></div><button type=""button"" onclick=""send()"">Click</button>
<script>
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message : ""Hello World""})
});
return response.json(); // parses JSON response into native JavaScript objects
}
function send(){
postData(window.location + 'greet', { answer: 42 })
.then(data => {
document.getElementById(""response"").innerHTML = data.message;
});
}
</script>
</body></html>
";
context.Response.Headers.Append("Content-Type", "text/html");
await context.Response.WriteAsync(page);
});
app.Run();
这段代码在 / 路由返回一个内嵌 HTML 页面,作用是浏览器内的自测工具:
- 页面上有一个 "Click" 按钮,点击后调用
send(); send()调用postData(window.location + 'greet', { answer: 42 }),向/greet发起fetchPOST 请求;- 请求头显式设置
'Content-Type': 'application/json',请求体为JSON.stringify({ message: "Hello World" }); - 响应被
response.json()解析后,把data.message写入页面上的#response元素。
值得注意的一个细节:postData 函数签名虽然接收 data 参数,但函数体内并未使用它,请求体是硬编码的 { message: "Hello World" }——所以真正发给服务端的是 message 字段,能与 Greeting.Message 正确绑定。{ answer: 42 } 这个参数实际上被忽略了。如果你想修改载荷,需要同时修改 postData 内的 body 一行。
与 map-post:两种处理器组织方式对比
仓库提供了两个几乎相同、仅在处理器组织方式上不同的示例,非常适合对照学习:
| 对比维度 | map-post | map-post-2 |
|---|---|---|
| 处理器定义 | 顶层局部函数 IResult Greet([FromBody] Greeting greet) | 静态类静态方法 MyApi.Greet |
| 注册方式 | app.MapPost("/greet", Greet) 直接引用函数名 | app.MapPost("/greet", MyApi.Greet) 引用方法组 |
| 适用场景 | 快速原型、单文件小工具 | 需要把处理器组织进类、便于测试与复用的场景 |
两者在端点行为上完全等价:路由 /greet、绑定 [FromBody] Greeting、返回 Results.Json 完全一致。区别纯粹是代码组织风格。当你处理多个端点、希望按领域归类,或希望像 typed-results-1 那样直接对处理器方法写单元测试时,静态方法方案(map-post-2)更合适。
显式与隐式:JSON 绑定的两种写法
仓库 minimal-api 目录 下的两个参数绑定示例,把 JSON 绑定的两种风格讲得很清楚:
- 显式绑定:parameter-binding-json-explicit 使用
([FromBody] Greeting greet),与本文 map-post-2 的写法一致,明确告诉框架该参数来自请求体; - 隐式绑定:parameter-binding-json-implicit 直接写
(Greeting greet),不标注任何属性,Minimal API 会依据默认规则推断:复杂类型参数自动从请求体按 JSON 反序列化。
从源码结构看,[FromBody] 的主要价值在于消除歧义:当处理器有多个参数(如同时有路由参数、查询参数和 body 参数)时,显式标注能让意图一目了然,也便于后续迁移到 MVC Controller 风格。
运行与验证
方式一:浏览器演示页
cd projects/minimal-api/map-post-2
dotnet run
启动后访问 http://localhost:5000/(默认端口以实际启动日志为准),点击页面上的 "Click" 按钮,#response 区域会显示服务端返回的 Hello World。
方式二:命令行验证(curl)
# 不带 Content-Type 或 JSON 载荷错误时,会得到 400
curl -X POST http://localhost:5000/greet \
-H "Content-Type: application/json" \
-d '{"message":"Hello World"}'
预期响应体为:
{"message":"Hello World"}
这验证了整条链路:JSON 请求体 → [FromBody] 反序列化为 Greeting → Results.Json 输出匿名对象。你也可以把 message 换成任意字符串(如 "你好,世界"),响应会原样回显。
扩展阅读:从 MapPost 到多动词处理
在掌握了 MapPost 之后,仓库还提供了两个紧密相关的进阶示例:
- map-methods 示例:用
app.MapMethods("/greet", new[] { "POST", "PUT", "PATCH" }, Greet)让同一个处理器同时服务多个 HTTP 动词,且同样支持[FromBody]JSON 绑定——只需把Greet的返回值从纯消息改为附带context.Request.Method即可区分来源动词; - typed-results-1 示例:把
Results.Json换成TypedResults.Json,返回值具备强类型JsonHttpResult<T>,可直接被测试代码断言,适合在静态方法组织方式下做单元测试。
小结
map-post-2 用一个 45 行的单文件示例,串起了 Minimal API 中最常用的三件套:MapPost 路由注册、[FromBody] JSON 模型绑定、Results.Json 响应输出,并示范了用静态方法组织处理器的最佳实践。对照同仓库的 map-post、map-methods、parameter-binding-json-explicit/implicit 等示例,你可以快速建立起 Minimal API 请求-绑定-响应全链路的完整认知,并将其直接迁移到自己的 .NET 10 项目中。
【免费下载链接】practical-aspnetcore
Practical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



