ASP.NET Core:自定义JsonConverter如何抛出多错误或访问ModelState?
解决方案:通过自定义模型绑定器实现多错误收集并写入ModelState
由于JsonConverter属于System.Text.Json序列化组件,本身无法直接访问ASP.NET Core的ModelState(两者上下文隔离),所以要实现无需包装类、直接将多个反序列化错误写入ModelState的需求,最直接的方式是用自定义模型绑定器替代原有转换器的逻辑,在绑定流程中完成反序列化重试、错误收集,并将错误注入ModelState。
具体实现步骤
1. 实现自定义模型绑定器
这个绑定器会执行你的反序列化重试逻辑,收集所有错误后直接添加到ModelState:
using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Text.Json; public class ErrorCollectingModelBinder<T> : IModelBinder where T : class, new() { public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); // 读取请求JSON内容 var request = bindingContext.HttpContext.Request; using var streamReader = new StreamReader(request.Body); var rawJson = await streamReader.ReadToEndAsync(); // 先验证JSON格式合法性 JsonDocument jsonDoc; try { jsonDoc = JsonDocument.Parse(rawJson); } catch (JsonException ex) { bindingContext.ModelState.TryAddModelError(string.Empty, $"JSON格式无效: {ex.Message}"); bindingContext.Result = ModelBindingResult.Failed(); return; } // 适配你原逻辑中的"model"属性提取(如果不需要可直接用root) var rootElement = jsonDoc.RootElement; var targetElement = rootElement.TryGetProperty("model", out var modelProp) ? modelProp : rootElement; var jsonObject = JsonObject.Create(targetElement); var errors = new List<(string PropertyPath, string Message)>(); var targetModel = new T(); // 循环重试反序列化,直到成功或无法继续修复 while (true) { try { targetModel = JsonSerializer.Deserialize<T>(jsonObject, new JsonSerializerOptions())!; break; } catch (JsonException ex) { var propertyPath = ExtractPropertyPathFromError(ex.Message); if (string.IsNullOrEmpty(propertyPath)) { errors.Add((string.Empty, ex.Message)); break; } // 收集错误(属性路径+错误消息) errors.Add((propertyPath, ex.Message)); // 移除问题属性,重试反序列化 if (!jsonObject.Remove(propertyPath)) { // 无法移除属性,避免死循环,退出 break; } } } // 将错误写入ModelState foreach (var error in errors) { // 转换路径格式(比如将$.[Age]转为Age,根据你的需求调整) var modelStateKey = error.PropertyPath.Replace("$.", "", StringComparison.OrdinalIgnoreCase); bindingContext.ModelState.TryAddModelError(modelStateKey, error.Message); } // 设置绑定成功的模型 bindingContext.Result = ModelBindingResult.Success(targetModel); } // 从JsonException消息中提取属性路径(适配你的错误格式) private string ExtractPropertyPathFromError(string errorMessage) { var pathMarkerIndex = errorMessage.IndexOf("Path: ", StringComparison.OrdinalIgnoreCase); if (pathMarkerIndex == -1) return string.Empty; var endIndex = errorMessage.IndexOf(" | ", pathMarkerIndex, StringComparison.OrdinalIgnoreCase); endIndex = endIndex == -1 ? errorMessage.Length : endIndex; return errorMessage.Substring(pathMarkerIndex + 6, endIndex - pathMarkerIndex - 6).Trim(); } }
2. 给目标模型绑定自定义绑定器
在你需要反序列化的模型类上添加[ModelBinder]属性,指定使用上面的绑定器:
using Microsoft.AspNetCore.Mvc; [ModelBinder(typeof(ErrorCollectingModelBinder<YourTargetModel>))] public class YourTargetModel { public int Id { get; set; } public string Username { get; set; } public int Age { get; set; } // 其他业务属性 }
3. 控制器中直接使用模型
在API接口中直接将模型作为参数,ModelState会自动包含所有反序列化错误:
[ApiController] [Route("api/[controller]")] public class DemoController : ControllerBase { [HttpPost] public IActionResult Create(YourTargetModel model) { if (!ModelState.IsValid) { // 直接返回包含所有错误的ModelState return BadRequest(ModelState); } // 正常处理业务逻辑 return Ok(model); } }
为什么不推荐在JsonConverter中实现?
JsonConverter的设计目标是处理序列化/反序列化的格式转换,它属于通用组件,不依赖ASP.NET Core的上下文,因此无法直接访问ModelState。强行通过静态变量、依赖注入传递上下文会导致代码耦合度高、线程安全问题,不符合组件设计原则。
内容的提问来源于stack exchange,提问作者SventoryMang
相关产品推荐
相关产品推荐

