如何实现跨多个类依赖属性场景下的ModelState验证?
实现方案
自定义跨类条件校验可以通过继承ValidationAttribute,从校验上下文获取根对象来实现,以下是可直接复用的代码:
方案1:固定规则的专用校验特性
直接针对你的业务场景实现专用特性,代码如下:
using System.ComponentModel.DataAnnotations; [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class IsRequiredOnlyWhenAIsNotNullAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // 从校验上下文获取根级Parent实例(.NET Core 3.0+ 直接支持RootObject属性 var parent = validationContext.RootObject as Parent; // 不在Parent校验上下文下直接跳过,当前校验规则不生效 if (parent == null) { return ValidationResult.Success; } // Child1为空或A属性无值时,无需校验B if (parent.C1 == null || string.IsNullOrWhiteSpace(parent.C1.A)) { return ValidationResult.Success; } // A有值时校验B是否为空 if (string.IsNullOrWhiteSpace(value?.ToString())) { return new ValidationResult(ErrorMessage ?? "当A属性有值时,B属性不能为空"); } return ValidationResult.Success; } }
方案2:通用跨属性路径校验特性
如果有大量同类规则的校验需求,可以实现通用特性支持配置依赖属性路径,无需重复定义特性:
using System.ComponentModel.DataAnnotations; using System.Reflection; [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class RequiredWhenPropertyHasValueAttribute : ValidationAttribute { private readonly string _dependentPropertyPath; public RequiredWhenPropertyHasValueAttribute(string dependentPropertyPath) { _dependentPropertyPath = dependentPropertyPath; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var rootObject = validationContext.RootObject; // 按路径解析依赖属性的值 var dependentValue = GetValueByPath(rootObject, _dependentPropertyPath); // 依赖属性无值时跳过校验 if (dependentValue == null || string.IsNullOrWhiteSpace(dependentValue.ToString())) { return ValidationResult.Success; } // 依赖属性有值时校验当前属性是否为空 if (string.IsNullOrWhiteSpace(value?.ToString())) { return new ValidationResult(ErrorMessage ?? $"{validationContext.DisplayName}为必填项"); } return ValidationResult.Success; } // 按点分隔的属性路径从对象中取值 private object GetValueByPath(object obj, string path) { foreach (var propName in path.Split('.')) { if (obj == null) return null; PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.Public | BindingFlags.Instance); if (prop == null) return null; obj = prop.GetValue(obj); } return obj; } }
使用方式如下:
public class Child2 { [RequiredWhenPropertyHasValue("C1.A", ErrorMessage = "当A属性有值时B不能为空")] public string B { get; set; } }
校验触发说明
- ASP.NET Core 控制器自动模型校验场景下直接生效,框架默认会递归校验子对象属性
- 手动触发校验时,调用代码如下:
var parent = new Parent(); // 填充parent对象赋值逻辑 var validationContext = new ValidationContext(parent); var validationResults = new List<ValidationResult>(); // 注意最后一个参数设为true开启全属性递归校验 Validator.TryValidateObject(parent, validationContext, validationResults, validateAllProperties: true);
- 低于.NET Core 3.0的版本没有
RootObject属性,可以手动将根对象赋值到validationContext.Items字典中传入,在校验逻辑中从Items取根对象即可。
内容的提问来源于stack exchange,提问作者Benjamin U.
相关产品推荐
相关产品推荐

