ASP.NET 6 WebAPI自定义JsonConverter转Password对象抛异常问题
问题解答
以下是对应问题的明确结论和可直接落地的实现方案:
合法性校验的正确实现方式
首先要划清职责边界:
- JsonConverter的
Read方法只负责格式层面的转换校验:先判断当前JSON Token类型是不是字符串,再调用Password.TryParse完成字符串到Password实例的转换。不要把依赖数据库、其他服务的业务校验(比如密码是否和历史重复、是否匹配账号对应规则)塞到Converter里,这类业务校验要放到Controller模型绑定完成后的阶段,用数据注解或者校验库实现即可。 - 你现在抛异常导致应用中断,根本不是校验逻辑本身的问题,是Program.cs里中间件顺序错误,或者接收参数的Controller没加
[ApiController]特性。.NET 6 WebAPI只要管线配置正确,反序列化阶段抛出的合规异常会被自动捕获,不会导致进程终止。 - 读取JSON值的时候必须先判断Token类型,否则传入非字符串值(比如数字、嵌套对象)时,直接调用
reader.GetString()会抛出不受框架识别的底层异常。
- JsonConverter的
校验失败的异常类型选择
校验失败必须抛内置的
System.Text.Json.JsonException,这是唯一被System.Text.Json序列化管线、ASP.NET Core模型绑定机制识别的请求错误类型,会被自动包装为400 Bad Request响应返回给客户端。
不要抛自定义异常、ArgumentException、ValidationException这类异常,这些会被框架判定为服务端内部错误,返回500状态码,未被全局异常处理捕获时就会出现你遇到的进程中断问题。
抛JsonException的时候建议带上当前JSON路径,方便前端快速定位错误字段:throw new JsonException("密码格式不合法,需满足8-16位,包含大小写字母与数字", reader.Path, null);不要在CanConvert方法里做值校验
完全不可行,原因有两个:
CanConvert方法的入参只有待转换的CLR类型,没有传入Utf8JsonReader实例,方法执行时JSON payload还没开始读取,你根本拿不到请求传递的密码字符串,没有做值校验的基础。CanConvert的执行结果会被序列化框架缓存,在应用启动阶段、序列化流程初始化时会被多次调用,在这里塞业务逻辑会严重拉低性能,甚至导致序列化缓存逻辑错乱。
修正后的完整实现代码
自定义JsonConverter代码
namespace TestApp.Converters { public class PasswordJsonConverter : JsonConverter<Password> { public override bool CanConvert(Type typeToConvert) { return typeToConvert == typeof(Password); } public override Password? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 先校验Token类型,避免非字符串值触发底层非预期异常 if (reader.TokenType != JsonTokenType.String) { throw new JsonException("密码字段必须是字符串类型", reader.Path, null); } string? input = reader.GetString(); if(!Password.TryParse(input, out Password pw)) { throw new JsonException("密码格式不符合要求,长度需为8-16位,同时包含大小写字母、数字", reader.Path, null); } return pw; } public override void Write(Utf8JsonWriter writer, Password value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); } } }
Program.cs 管线正确配置(注意中间件顺序)
var builder = WebApplication.CreateBuilder(args); // 注册控制器服务,注入自定义JsonConverter builder.Services.AddControllers() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new PasswordJsonConverter()); }); var app = builder.Build(); // 异常处理中间件必须放在所有其他业务中间件的最前面 if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
注意:所有接收Password类型参数的Controller必须标注
[ApiController]特性,该特性自带的模型验证过滤器会自动捕获反序列化阶段的JsonException,统一返回结构化的400错误响应,不会让异常冒泡导致应用中断。
内容的提问来源于stack exchange,提问作者user9573909
相关产品推荐
相关产品推荐

