C# JsonPatchDocument能否在ModelState无效时设置自定义错误消息
C# JsonPatchDocument自定义DateTime类型校验错误消息实现
场景说明
C#接口使用JsonPatchDocument处理补丁请求时,若传入类似77-77-7777的无效日期值,接口默认返回的400错误提示为固定格式:
{ "errors": { "AccountDto": [ "The value '77-77-7777' is invalid for target location." ] }, "title": "One or more validation errors occurred.", "status": 400 }
该提示无法体现具体出错属性,需要将所有DateTime类型属性的该类校验错误调整为{propertyName} input value '{invalidValue}' is an invalid date格式。
实现方式
这个错误是JsonPatch在执行路径值类型转换阶段写入ModelState的,不属于普通数据注解校验范围,可以通过自定义ApiBehaviorOptions的无效模型状态响应逻辑实现消息替换,不需要修改原有接口业务代码。
- 首先在Program.cs服务配置段,添加自定义的InvalidModelState处理逻辑:
builder.Services.Configure<ApiBehaviorOptions>(options => { options.InvalidModelStateResponseFactory = context => { foreach (var key in context.ModelState.Keys) { var stateEntry = context.ModelState[key]; if (stateEntry?.Errors == null) continue; for (var i = 0; i < stateEntry.Errors.Count; i++) { var error = stateEntry.Errors[i]; // 匹配JsonPatch类型转换失败的默认错误模板 if (!string.IsNullOrEmpty(error.ErrorMessage) && error.ErrorMessage.StartsWith("The value '") && error.ErrorMessage.EndsWith("' is invalid for target location.")) { // 提取用户传入的无效原始值 var invalidValue = error.ErrorMessage .Replace("The value '", string.Empty) .Replace("' is invalid for target location.", string.Empty); // 获取当前Patch操作对应的目标属性类型 var targetPropType = GetPatchTargetPropType(context, key); if (targetPropType == typeof(DateTime) || targetPropType == typeof(DateTime?)) { // 替换为自定义错误消息 var propName = key.Split('.').Last(); stateEntry.Errors[i] = new ModelError($"{propName} input value '{invalidValue}' is an invalid date"); } } } } // 保留原有400响应结构,仅替换错误提示内容 return new BadRequestObjectResult(new ValidationProblemDetails(context.ModelState) { Status = StatusCodes.Status400BadRequest, Title = "One or more validation errors occurred." }); }; });
- 添加辅助方法,用来解析Patch路径对应的目标属性类型:
static Type? GetPatchTargetPropType(ActionContext context, string modelStateKey) { try { // 查找接口参数中定义的JsonPatchDocument对应的目标Dto类型 var patchParameter = context.ActionDescriptor.Parameters .FirstOrDefault(p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(JsonPatchDocument<>)); if (patchParameter == null) return null; var dtoType = patchParameter.ParameterType.GetGenericArguments()[0]; var propName = modelStateKey.Split('.').Last(); // 查找属性对应类型,若使用了自定义序列化命名,可在此处增加名称匹配逻辑 return dtoType.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)?.PropertyType; } catch { return null; } }
补充说明
- 如果项目使用了小驼峰、下划线等自定义Json命名策略,反射查找属性时需要对应做名称映射,匹配
[JsonPropertyName]等特性配置的属性名 - 需要支持DateTimeOffset类型校验的话,只要在类型判断逻辑中追加
typeof(DateTimeOffset)、typeof(DateTimeOffset?)即可 - 该逻辑仅替换DateTime类型对应的JsonPatch转换错误,不影响其他类型校验、其他业务校验的原有提示
内容的提问来源于stack exchange,提问作者user3261212
相关产品推荐
相关产品推荐

