.NET Core中ApiController模型验证与TryValidateObject的差异问题
问题解答
验证行为差异的原因
原生Validator.TryValidateObject即使将validateAllProperties参数设为true,也仅会验证根对象的直接属性,不会递归遍历复杂类型的子属性执行校验。你的示例中,它只会检查Order对象的Recipient不为空、Product属性不为null,不会深入Product对象内部校验Code字段是否符合[Required]规则。
ASP.NET Core控制器的模型验证系统单独实现了全对象图递归验证逻辑,会遍历所有嵌套的复杂类型、集合类型的子元素执行校验,还会自动拼接属性路径(如Product.Code)生成错误信息,因此可以正确检测到嵌套字段的校验问题。
服务层实现递归验证的方案
针对你需要在共享类库中提前校验对象的场景,有两种常用落地方案:
方案一:自定义递归验证器(无ASP.NET Core依赖)
自己实现递归验证逻辑,支持嵌套复杂类型、集合类型的校验,且错误属性路径格式和控制器返回结果完全一致:
using System.Collections; using System.ComponentModel.DataAnnotations; using System.Reflection; public static class RecursiveValidator { public static bool TryValidateObjectRecursive(object obj, List<ValidationResult> results, string propertyPrefix = "") { if (obj == null) return true; bool isValid = true; // 验证当前对象的直接属性 var validationContext = new ValidationContext(obj, serviceProvider: null, items: null); isValid &= Validator.TryValidateObject(obj, validationContext, results, validateAllProperties: true); // 遍历所有非值类型、非字符串的属性,递归校验 var properties = obj.GetType().GetProperties() .Where(p => p.CanRead && !p.PropertyType.IsValueType && p.PropertyType != typeof(string)); foreach (var property in properties) { var propertyValue = property.GetValue(obj); if (propertyValue == null) continue; // 处理集合类型属性 if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) { int index = 0; foreach (var item in (IEnumerable)propertyValue) { string itemPrefix = $"{propertyPrefix}{property.Name}[{index}]."; var itemResults = new List<ValidationResult>(); isValid &= TryValidateObjectRecursive(item, itemResults, itemPrefix); // 拼接属性路径后加入全局结果 foreach (var result in itemResults) { results.Add(new ValidationResult( result.ErrorMessage, result.MemberNames.Select(member => $"{itemPrefix}{member}") )); } index++; } } // 处理单个复杂类型属性 else { string subPrefix = $"{propertyPrefix}{property.Name}."; var subResults = new List<ValidationResult>(); isValid &= TryValidateObjectRecursive(propertyValue, subResults, subPrefix); foreach (var result in subResults) { results.Add(new ValidationResult( result.ErrorMessage, result.MemberNames.Select(member => $"{subPrefix}{member}") )); } } } return isValid; } }
调用方式如下:
Order order = new Order() { Recipient = "John Doe", Product = new Product() { Manufacturer = "Company Inc.", Code = string.Empty } }; var validationResults = new List<ValidationResult>(); bool isValid = RecursiveValidator.TryValidateObjectRecursive(order, validationResults);
调用后isValid会返回false,validationResults中会包含Product.Code字段的必填错误信息。
方案二:复用ASP.NET Core模型验证器(行为100%匹配服务端)
如果你的共享项目可以依赖ASP.NET Core相关包,直接引用Microsoft.AspNetCore.Mvc.Core包,复用框架自带的IObjectModelValidator完成校验,完全和服务端控制器的验证行为一致,不需要自己处理递归逻辑的边界问题。
内容的提问来源于stack exchange,提问作者Aleks Vujic
相关产品推荐
相关产品推荐

