.NET Core 2.0如何实现控制器模型验证短路?
实现.NET Core 2.0模型验证的短路行为
当然可以实现!在.NET Core 2.0中,默认的模型验证会执行所有验证规则并收集全部错误,但通过自定义验证逻辑或者扩展默认的验证机制,就能实现「前置验证失败就停止后续验证」的短路效果。下面给你两种实用方案,按需选择:
方案一:针对单个DTO手动控制验证顺序(灵活定制)
如果只是个别DTO需要短路验证,让DTO实现IValidatableObject接口,在Validate方法里按你想要的顺序逐个验证属性规则,一旦发现错误就直接终止后续验证。
举个例子:
public class UserCreateDto : IValidatableObject { // 这里的DataAnnotations可以保留,但实际验证逻辑我们手动控制 [Required] [MaxLength(20)] public string Username { get; set; } [Required] [EmailAddress] public string Email { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { // 先验证Username必填 if (string.IsNullOrWhiteSpace(Username)) { yield return new ValidationResult("用户名不能为空", new[] { nameof(Username) }); // 直接停止后续验证,返回错误 yield break; } // 必填通过后,再验证Username长度 if (Username.Length > 20) { yield return new ValidationResult("用户名长度不能超过20个字符", new[] { nameof(Username) }); } // 接着验证Email必填 if (string.IsNullOrWhiteSpace(Email)) { yield return new ValidationResult("邮箱不能为空", new[] { nameof(Email) }); yield break; } // 必填通过后,验证邮箱格式 var emailRegex = new Regex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$"); if (!emailRegex.IsMatch(Email)) { yield return new ValidationResult("邮箱格式不正确", new[] { nameof(Email) }); } } }
这种方式的优势是完全自定义验证顺序和短路逻辑,缺点是每个需要短路的DTO都要编写Validate方法,适合局部场景。
方案二:全局替换验证提供者(统一生效)
如果整个项目都需要验证短路的行为,我们可以自定义ModelValidatorProvider,替换默认的验证逻辑,让所有属性的验证器按顺序执行,一旦失败就停止后续验证。
步骤1:创建短路模型验证器
这个验证器会遍历属性的所有验证规则,执行第一个失败就返回结果:
public class ShortCircuitModelValidator : IModelValidator { private readonly IEnumerable<IModelValidator> _validators; public ShortCircuitModelValidator(IEnumerable<IModelValidator> validators) { _validators = validators; } public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context) { foreach (var validator in _validators) { var validationResults = validator.Validate(context).ToList(); if (validationResults.Any()) { // 发现错误,返回结果并不再执行后续验证器 return validationResults; } } return Enumerable.Empty<ModelValidationResult>(); } public bool IsRequired => _validators.Any(v => v.IsRequired); }
步骤2:创建自定义验证提供者
这个提供者会拦截默认的验证器,用我们的短路验证器包装多个验证规则:
public class ShortCircuitModelValidatorProvider : IModelValidatorProvider { public void CreateValidators(ModelValidatorProviderContext context) { // 收集当前属性的所有默认验证器 var originalValidators = context.Results.Select(item => item.Validator).ToList(); context.Results.Clear(); if (originalValidators.Count > 1) { // 用短路验证器包装多个验证规则 context.Results.Add(new ValidatorItem { Validator = new ShortCircuitModelValidator(originalValidators), IsReusable = true }); } else { // 只有单个验证器,直接放回原结果 context.Results.AddRange(context.Results); } } }
步骤3:在Startup中注册提供者
在ConfigureServices里将自定义提供者添加到MVC选项中,确保它优先执行:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { // 插入自定义验证提供者到最前面,覆盖默认行为 options.ModelValidatorProviders.Insert(0, new ShortCircuitModelValidatorProvider()); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_0); }
注意事项
- 验证顺序:默认情况下,DataAnnotations的验证顺序和你在属性上标注的顺序一致,自定义提供者会保留这个顺序,所以确保你把优先级高的验证(比如
Required)放在前面。 - 类级别验证:类级别的验证属性(比如作用在DTO类上的验证)会在所有属性验证完成后执行,如果需要连类级别验证也短路,需要额外在自定义验证器中处理类级别的验证规则顺序。
内容的提问来源于stack exchange,提问作者Daryl
相关产品推荐
相关产品推荐

