直接用problemdetails类构造并返回是最稳妥、轻量且符合rfc 7807的方式;badrequest(new { message = "xxx" })绕过标准,导致type/title/status等字段丢失、状态码固定为400、易泄露敏感信息。

直接用 ProblemDetails 类构造并返回,是目前最稳妥、最轻量、也最符合 RFC 7807 的方式——它不依赖第三方库,不需额外配置,Content-Type 自动设为 application/problem+json,状态码也和 Status 字段严格对齐。
为什么不能只写 BadRequest(new { message = "xxx" })
这种写法绕过了 RFC 7807 标准,客户端(尤其是 OpenAPI 生成的 client 或 axios 的 isAxiosError)无法按规范解析关键字段:
-
type、title、instance等字段完全丢失,只剩一个扁平的 JSON 对象 -
Status字段被忽略,响应码永远是 400,哪怕你本意是返回422(校验失败)或409(冲突) - 开发环境若未关掉
UseDeveloperExceptionPage(),错误体可能混入堆栈、路径、数据库名等敏感信息
ProblemDetails 实例怎么构造才安全
必须显式设置 Status,且返回时用 ObjectResult 或 StatusCode,避免用 BadRequest/NotFound 这类快捷方法:
return new ObjectResult(new ProblemDetails { Type = "https://example.com/errors/validation-failed", Title = "验证失败", Status = 422, Detail = "价格必须为正数", Instance = Request.Path });- 若用
ActionResult<t></t>,推荐return StatusCode(problemDetails.Status.Value, problemDetails);,而不是return BadRequest(problemDetails) -
Type建议用可访问的 URL(哪怕只是占位),别填空字符串或 null;Instance应设为当前请求路径,便于追踪
自定义扩展字段时,哪些名字绝对不能碰
RFC 7807 允许加任意扩展属性,但前提是不能覆盖标准字段。常见翻车点:
- 继承
ProblemDetails后,加了public string type { get; set; }——这会遮蔽基类的Type属性,序列化后出现两个type字段,客户端解析失败 - 扩展字段用了
status、detail、title等小写名,和框架默认字段冲突,JSON 序列化器行为不可控 - 想加验证错误明细?用
public Dictionary<string string> Errors { get; set; }</string>,首字母大写,避开保留名
最容易被忽略的是中间件顺序和开发/生产环境切换逻辑:如果 UseExceptionHandler("/error") 放在 UseRouting() 之前,或者没用 env.IsDevelopment() 包裹 UseDeveloperExceptionPage(),那连 404 都进不了你的错误处理器。










