ASP.NET MVC 条件验证

Posted

技术标签:

【中文标题】ASP.NET MVC 条件验证【英文标题】:ASP.NET MVC Conditional validation 【发布时间】:2011-01-25 21:41:24 【问题描述】:

如何使用数据注解对模型进行条件验证?

例如,假设我们有以下模型(Person 和 Senior):

public class Person

    [Required(ErrorMessage = "*")]
    public string Name
    
        get;
        set;
    

    public bool IsSenior
    
        get;
        set;
    

    public Senior Senior
    
        get;
        set;
    


public class Senior

    [Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
    public string Description
    
        get;
        set;
    

还有以下观点:

<%= html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>

<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>

<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>

我想成为基于“IsSenior”属性选择的“Senior.Description”属性条件必填字段(true -> 必需)。如何在 ASP.NET MVC 2 中使用数据注释实现条件验证?

【问题讨论】:

我最近问过类似的问题:***.com/questions/2280539/… 我很困惑。 Senior 对象始终是高级对象,那么在这种情况下,为什么 IsSenior 可以为假。当Person.IsSenior 为假时,您是否只需要'Person.Senior' 属性为空。或者为什么不按如下方式实现IsSenior 属性:bool IsSenior get return this.Senior != null; Steven:“IsSenior”转换为视图中的复选框字段。当用户选中“IsSenior”复选框时,“Senior.Description”字段成为必填项。 达林·迪米特洛夫:不错,但不完全是。您看,您将如何实现将错误消息附加到特定字段?如果在对象级别进行验证,则会在对象级别收到错误。我需要属性级别的错误。 【参考方案1】:

在 MVC3 中添加条件验证规则有更好的方法;让您的模型继承 IValidatableObject 并实现 Validate 方法:

public class Person : IValidatableObject

    public string Name  get; set; 
    public bool IsSenior  get; set; 
    public Senior Senior  get; set; 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
     
        if (IsSenior && string.IsNullOrEmpty(Senior.Description)) 
            yield return new ValidationResult("Description must be supplied.");
    

在Introducing ASP.NET MVC 3 (Preview 1)了解更多信息。

【讨论】:

不幸的是,微软把它放在了错误的层——验证是业务逻辑,这个接口在 System.Web DLL 中。为了使用它,您必须让您的业务层依赖于表示技术。 如果你实现了它,你就可以做 - 请参阅falconwebtech.com/post/…的完整示例 falconwebtech.com/post/… - @viperguynaz 这不起作用 @RayLoveless 你应该打电话给ModelState.IsValid - 不要直接调用Validate 我知道这是一个较旧的线程,但要完成 - @viperguynaz - 将错误绑定到特定属性(例如,用于客户端模型绑定验证错误显示),请改用重载方法。 "yield return new ValidationResult("ErrorMessage.", new[] "PutNameOfPropertyHere"); 没有这个,验证错误是通用的,需要在视图中显示一个全面的警告标签。【参考方案2】:

我通过处理控制器包含的"ModelState" 字典解决了这个问题。 ModelState 字典包含所有需要验证的成员。

解决办法如下:

如果您需要基于某个字段实现条件验证(例如,如果 A=true,则需要 B),同时保持属性级别的错误消息(此对于对象级别的自定义验证器而言并非如此)您可以通过处理“ModelState”来实现这一点,只需从中删除不需要的验证。

...在某个班级...

public bool PropertyThatRequiredAnotherFieldToBeFilled

  get;
  set;


[Required(ErrorMessage = "*")] 
public string DepentedProperty

  get;
  set;

...继续上课...

...在某些控制器操作中...

if (!PropertyThatRequiredAnotherFieldToBeFilled)

   this.ModelState.Remove("DepentedProperty");

...

这样我们实现了条件验证,而其他一切都保持不变。


更新:

这是我的最终实现:我在模型上使用了一个接口,并使用了用于验证实现所述接口的模型的 action 属性。接口规定了 Validate(ModelStateDictionary modelState) 方法。 action 上的属性只是调用 IValidatorSomething 上的 Validate(modelState)。

我不想让这个答案复杂化,所以我没有提到最终的实现细节(最后,这在生产代码中很重要)。

【讨论】:

缺点是您的验证逻辑一部分位于模型中,另一部分位于控制器中。 当然这不是必须的。我只展示最基本的例子。我已经使用模型上的接口和验证实现上述接口的模型的动作属性实现了这一点。接口出汗 Validate(ModelStateDictionary modelState) 方法。所以最后你在模型中做所有的验证。无论如何,好点。 我喜欢这种方法的简单性,直到 MVC 团队开箱即用地构建出更好的东西。但是您的解决方案是否可以在启用客户端验证的情况下工作?? @Aaron:我很高兴你喜欢这个解决方案,但不幸的是,这个解决方案不适用于客户端验证(因为每个验证属性都需要其 javascript 实现)。您可以使用“Remote”属性来帮助自己,因此只会发出 Ajax 调用来验证它。 你能扩展这个答案吗?这是有道理的,但我想确保我对此很清楚。我正面临这种确切的情况,我想解决它。【参考方案3】:

我昨天遇到了同样的问题,但我以一种非常干净的方式完成了它,它适用于客户端和服务器端验证。

条件:基于模型中其他属性的值,您想使其他属性成为必需。这是代码

public class RequiredIfAttribute : RequiredAttribute

    private String PropertyName  get; set; 
    private Object DesiredValue  get; set; 

    public RequiredIfAttribute(String propertyName, Object desiredvalue)
    
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
    

    protected override ValidationResult IsValid(object value, ValidationContext context)
    
        Object instance = context.ObjectInstance;
        Type type = instance.GetType();
        Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
        if (proprtyvalue.ToString() == DesiredValue.ToString())
        
            ValidationResult result = base.IsValid(value, context);
            return result;
        
        return ValidationResult.Success;
    

这里的 PropertyName 是您要设置条件的属性 DesiredValue 是您的其他属性需要验证的 PropertyName(属性)的特定值

假设你有以下内容

public class User

    public UserType UserType  get; set; 

    [RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
    public string Password
    
        get;
        set;
    

最后但并非最不重要的是,为您的属性注册适配器,以便它可以进行客户端验证(我将它放在 global.asax,Application_Start 中)

 DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));

【讨论】:

这是最初的起点miroprocessordev.blogspot.com/2012/08/… asp.net mvc2 中是否有等效的解决方案? ValidationResult、ValidationContext 类在 asp.net mvc2 (.net framework 3.5) 中不可用 这仅适用于链接博客状态的服务器端 我设法通过 MVC5 在客户端实现了这个工作,但在客户端,无论 DesiredValue 是什么,它都会启动验证。 @Dan Hunex :在 MVC4 中,我无法在客户端正常工作,无论 DesiredValue 是什么,它都会启动验证。有什么帮助吗?【参考方案4】:

我一直在使用这个神奇的 nuget 来做动态注释 ExpressiveAnnotations

您可以验证您梦寐以求的任何逻辑:

public string Email  get; set; 
public string Phone  get; set; 
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact  get; set; 

【讨论】:

ExpressiveAnnotation 库是这里所有答案中最灵活、最通用的解决方案。感谢分享! 我一直在努力寻找解决方案,让我度过充实的一天。 ExpressiveAnnotations 看起来可以解决我的问题! ExpressiveAnnotation 库太棒了! 它也有客户端支持! 虽然不支持 .NET Core,而且看起来不会发生。【参考方案5】:

您可以通过从 ModelState 中删除错误来有条件地禁用验证器:

ModelState["DependentProperty"].Errors.Clear();

【讨论】:

【参考方案6】:

谢谢Merritt :)

我刚刚将其更新为 MVC 3,以防有人发现它有用:Conditional Validation in ASP.NET MVC 3。

【讨论】:

【参考方案7】:

现在有一个框架可以开箱即用地执行此条件验证(以及其他方便的数据注释验证): http://foolproof.codeplex.com/

具体来说,看看 [RequiredIfTrue("IsSenior")] 验证器。您将其直接放在要验证的属性上,因此您可以获得与“高级”属性相关联的验证错误的所需行为。

它以 NuGet 包的形式提供。

【讨论】:

【参考方案8】:

您需要在 Person 级别进行验证,而不是在 Senior 级别进行验证,或者 Senior 必须具有对其父 Person 的引用。在我看来,您需要一种自我验证机制来定义对 Person 的验证,而不是在它的某个属性上。我不确定,但我认为 DataAnnotations 不支持开箱即用。您可以创建自己的Attribute,它派生自ValidationAttribute,可以在类级别进行修饰,然后创建一个自定义验证器,该验证器还允许这些类级别的验证器运行。

我知道 Validation Application Block 支持开箱即用的自我验证,但 VAB 的学习曲线相当陡峭。不过,这里有一个使用 VAB 的示例:

[HasSelfValidation]
public class Person

    public string Name  get; set; 
    public bool IsSenior  get; set; 
    public Senior Senior  get; set; 

    [SelfValidation]
    public void ValidateRange(ValidationResults results)
    
        if (this.IsSenior && this.Senior != null && 
            string.IsNullOrEmpty(this.Senior.Description))
        
            results.AddResult(new ValidationResult(
                "A senior description is required", 
                this, "", "", null));
        
    

【讨论】:

“您需要在 Person 级别验证,而不是在 Senior 级别验证” 是的,这是一个选项,但是您失去了将错误附加到特定字段的能力,这在 Senior 对象中是必需的。 【参考方案9】:

我遇到了同样的问题,需要修改 [Required] 属性 - 使字段依赖于 http 请求。解决方案类似于 Dan Hunex 的答案,但他的解决方案无法正常工作(请参阅 cmets)。我不使用不显眼的验证,只使用 MicrosoftMvcValidation.js 开箱即用。 这里是。实现您的自定义属性:

public class RequiredIfAttribute : RequiredAttribute


    public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
    

    

    protected override ValidationResult IsValid(object value, ValidationContext context)
    

    //You can put your logic here   

        return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
    



然后您需要实现您的自定义提供程序以将其用作 global.asax 中的适配器

public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>


    ControllerContext ccontext;
    public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
       : base(metadata, context, attribute)
    
        ccontext = context;// I need only http request
    

//override it for custom client-side validation 
     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
            
               //here you can customize it as you want
         ModelClientValidationRule rule = new ModelClientValidationRule()
         
             ErrorMessage = ErrorMessage,
    //and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"    
             ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
         ;
         return new ModelClientValidationRule[]  rule ;
      

并用一行修改你的 global.asax

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));

就在这里

[RequiredIf]
public string NomenclatureId  get; set; 

对我来说的主要优势是我不必像在不显眼的验证中那样编写自定义客户端验证器。它的工作原理与 [必需] 一样,但仅限于您想要的情况。

【讨论】:

扩展DataAnnotationsModelValidator的部分正是我需要看到的。谢谢。【参考方案10】:

查看 Simon Ince 的Conditional Validation in MVC。

我现在正在研究他的示例项目。

【讨论】:

【参考方案11】:

从模型状态有条件地移除错误的典型用法:

    使控制器动作的第一部分有条件 执行逻辑以从 ModelState 中删除错误 执行现有逻辑的其余部分(通常是模型状态验证,然后是其他所有操作)

例子:

public ActionResult MyAction(MyViewModel vm)

    // perform conditional test
    // if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")

    // Do typical model state validation, inside following if:
    //     if (!ModelState.IsValid)

    // Do rest of logic (e.g. fetching, saving

在您的示例中,保持一切原样并将建议的逻辑添加到控制器的操作中。我假设传递给控制器​​操作的 ViewModel 具有 Person 和 Senior Person 对象,其中包含从 UI 填充的数据。

【讨论】:

【参考方案12】:

我正在使用 MVC 5,但您可以尝试以下方法:

public DateTime JobStart  get; set; 

[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate  get; set; 

在您的情况下,您会说“IsSenior == true”。 然后你只需要检查你的帖子操作的验证。

【讨论】:

以上是关于ASP.NET MVC 条件验证的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS + ASP.NET Web API + ASP.NET MVC 身份验证

Asp.net MVC的验证

ASP.NET MVC

ASP.NET MVC:窗体身份验证及角色权限管理示例

ASP.net 验证码(C#) MVC

ASP.NET MVC 输入验证