ASP.NET Core 2.0如何在ModelState验证前过滤指定属性?
针对你在ASP.NET Core 2.0里遇到的问题——根据Type属性值动态排除某些属性的ModelState验证,这里有几个实用的方案,完全不需要用@Html.HiddenFor():
方案1:自定义条件验证属性(最优雅的属性级控制)
这个方案直接在ViewModel的属性上标记条件验证规则,让验证逻辑和模型绑定在一起,完全符合ASP.NET Core的验证流程,而且是在验证过程中直接跳过不需要的验证,不是事后移除错误。
首先创建一个RequiredIfNotAttribute(用来实现“当某个属性不等于指定值时,当前属性必填”):
using System.ComponentModel.DataAnnotations; using System.Reflection; public class RequiredIfNotAttribute : ValidationAttribute { private readonly string _dependentPropertyName; private readonly object _excludedValue; public RequiredIfNotAttribute(string dependentPropertyName, object excludedValue) { _dependentPropertyName = dependentPropertyName; _excludedValue = excludedValue; ErrorMessage = "{0} is required when {1} is not {2}"; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { // 获取依赖属性的信息 var dependentProperty = validationContext.ObjectType.GetProperty(_dependentPropertyName); if (dependentProperty == null) { return new ValidationResult($"无法找到依赖属性:{_dependentPropertyName}"); } // 获取依赖属性的当前值 var dependentValue = dependentProperty.GetValue(validationContext.ObjectInstance); // 如果依赖属性不等于排除值,就验证当前属性是否为空 if (!Equals(dependentValue, _excludedValue)) { if (value == null || (value is string str && string.IsNullOrEmpty(str))) { return new ValidationResult( string.Format(ErrorMessage, validationContext.DisplayName, _dependentPropertyName, _excludedValue) ); } } return ValidationResult.Success; } }
然后在你的ViewModel里使用这个属性:
public class YourViewModel { public int Type { get; set; } // 当Type不等于1时,Key属性才需要验证;Type=1时自动跳过验证 [RequiredIfNot(nameof(Type), 1)] public string Key { get; set; } // 其他类似需求的属性也可以用同样方式处理 [RequiredIfNot(nameof(Type), 2)] public string AnotherProperty { get; set; } // 正常的必填属性不受影响 [Required] public string BasicProperty { get; set; } }
这样ASP.NET Core在验证模型时,会自动根据Type的值判断是否需要验证Key和其他属性,完全不需要额外的Filter处理。
方案2:使用ActionFilter移除指定属性的验证错误(快速实现)
如果你不想自定义验证属性,也可以用ActionFilter在ModelState验证完成后,动态移除不需要的错误。虽然是事后处理,但实现简单,适合快速迭代:
首先创建一个Filter:
using Microsoft.AspNetCore.Mvc.Filters; public class ConditionalValidationFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { // 从Action参数中获取你的ViewModel实例 if (context.ActionArguments.TryGetValue("model", out var modelObj) && modelObj is YourViewModel model) { // 当Type=1时,移除Key属性的所有验证错误 if (model.Type == 1) { context.ModelState.Remove(nameof(YourViewModel.Key)); } // 其他条件判断,比如Type=2时移除AnotherProperty的错误 if (model.Type == 2) { context.ModelState.Remove(nameof(YourViewModel.AnotherProperty)); } } base.OnActionExecuting(context); } }
然后在Startup.cs里注册这个Filter(因为用了ServiceFilter,所以需要注册为Scoped):
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddScoped<ConditionalValidationFilter>(); }
最后在你的Action或者Controller上标记这个Filter:
[ServiceFilter(typeof(ConditionalValidationFilter))] public IActionResult Submit(YourViewModel model) { if (!ModelState.IsValid) { // 返回错误视图或JSON return View(model); } // 业务逻辑处理 return RedirectToAction("Success"); }
这个方案的好处是不需要修改ViewModel的验证属性,直接在Filter里统一处理所有条件逻辑,适合多个ViewModel共享相同验证规则的场景。
方案3:实现IValidatableObject(模型级统一验证)
如果你的条件验证逻辑比较复杂,涉及多个属性的联动,可以让ViewModel实现IValidatableObject接口,在Validate方法里编写所有条件验证逻辑:
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; public class YourViewModel : IValidatableObject { public int Type { get; set; } // 这里不要加任何验证属性,所有验证逻辑放在Validate方法里 public string Key { get; set; } public string AnotherProperty { get; set; } [Required] public string BasicProperty { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { // 当Type不等于1时,验证Key是否为空 if (Type != 1 && string.IsNullOrEmpty(Key)) { yield return new ValidationResult( "Key不能为空(当Type不等于1时)", new[] { nameof(Key) } ); } // 当Type不等于2时,验证AnotherProperty是否为空 if (Type != 2 && string.IsNullOrEmpty(AnotherProperty)) { yield return new ValidationResult( "AnotherProperty不能为空(当Type不等于2时)", new[] { nameof(AnotherProperty) } ); } // 其他复杂的联动验证逻辑 } }
这个方案适合逻辑复杂的场景,所有验证逻辑集中在一个方法里,便于维护。
内容的提问来源于stack exchange,提问作者Ricky Suhendar

