actionresult无法提供前端所需的code、message、data三层结构,必须封装泛型apiresult并配合actionfilterattribute全局包装,同时需显式标注producesresponsetype以确保swagger和nswag正确生成文档与客户端。

直接用 ActionResult<t></t> 或 IActionResult 返回数据,前端收不到 Code、Message、Data 三层结构,没法做统一错误拦截和 loading 控制——必须封装自己的响应类型,并在序列化前介入。
定义泛型 ApiResult<tdata></tdata> 时三个关键约束
不加泛型约束或乱用 object,会导致反序列化后前端拿不到真实类型、值类型字段默认值崩掉、null 数据被忽略:
-
Data字段必须声明为TData?(C# 10+ 可空引用上下文开启时),且类定义加上where TData : class或where TData : struct显式区分;否则ApiResult<int>.Data</int>在未赋值时是0而非null,掩盖业务空值语义 -
Code别用枚举硬编码到类里,单独建静态类ApiResultCode,比如public static readonly int ParamError = 40001;,避免控制器里散落魔数 - 构造函数中对
Message赋默认值= string.Empty,防止 JSON 序列化出"Message": null,前端解构报错
全局自动包装要绕过 ASP.NET Core 的 ObjectResult 自动转换
中间件改 HttpResponse.Body 行不通——那时已经序列化完了。真正生效的位置是 Action 执行完、但还没进 JSON 输出器的阶段:
- 写一个继承
ActionFilterAttribute的过滤器,在OnResultExecutionAsync里判断context.Result is ObjectResult or OkObjectResult - 跳过已包装过的响应:
if (result.Value is ApiResult)就直接 return,否则才用反射构造ApiResult<t>.Success(value)</t> - 别碰
EmptyResult、StatusCodeResult、NotFoundResult——这些是明确要返回原始 HTTP 状态码的,强行包会把 404 变成 200 + {Code:404},破坏 REST 语义
NSwag 生成客户端时如何保持包装结构不失真
NSwag 默认把 ApiResult<user></user> 当作顶层响应类型,但若你在 Controller 里写 return Ok(new User()),NSwag 会认为接口返回的是 User,而不是 ApiResult<user></user> —— 它看不到你过滤器里的包装逻辑:
- 在 NSwagStudio 的
Response Wrapping设置里勾选Wrap Responses,并填入你的包装类全名,例如Pgy_Wx.Common.ApiResult`1(注意反引号和数字 1 表示泛型) - 同时勾选
Use Response Wrapper for All Operations,否则只对标注了[ProducesResponseType]的方法生效 - 生成的 TypeScript 客户端会自动把
data字段解包一层,但 C# 客户端不会——你得手动在 NSwag 配置里加GenerateClientInterfaces: true并配合UseHttpClient: true才能获得强类型ApiResult<t></t>返回
最容易被忽略的是:过滤器包装后,Swagger UI 里看到的 Schema 还是裸 User,不是 ApiResult<user></user>。这不代表错了,只是 NSwag 没法自动推导运行时包装行为——你得靠 [ProducesResponseType(typeof(ApiResult<user>))]</user> 显式告诉它,否则文档和客户端代码就对不上。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











