ASP.NET MVC条件验证


129

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

例如,假设我们有以下模型(人员和高级):

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”属性(true-> required)的选择的“ Senior.Description”属性条件必填字段。如何在带有数据注释的ASP.NET MVC 2中实现条件验证?



我很困惑。一个Senior对象始终是一个高级对象,那么在这种情况下,为什么IsSenior可以为false。当Person.IsSeniorfalse 时,您是否只需要'Person.Senior'属性为null即可。或为什么不IsSenior按以下方式实现该属性:bool IsSenior { get { return this.Senior != null; } }
史蒂文

史蒂文:“ IsSenior”转换为视图中的复选框字段。当用户选中“ IsSenior”复选框时,“ Senior.Description”字段将变为必填字段。
Peter Stegnar 2010年

达林·迪米特洛夫(Darin Dimitrov):很好,但不完全是。您看到,如何将错误消息应用于特定字段?如果在对象级别进行验证,则会在对象级别收到错误。我需要属性级别的错误。
Peter Stegnar 2010年

Answers:


150

有一种更好的方法可以在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.");
    }
}

介绍ASP.NET MVC 3(预览1)中了解更多信息。


如果财产是“int”类型,需要的值,如果填充该字段,验证不起作用..
Jeyhun拉希莫夫

2
不幸的是,Microsoft将其置于错误的层中-验证是业务逻辑,而此接口位于System.Web DLL中。为了使用此功能,您必须使您的业务层依赖于表示技术。
NightOwl888

7
如果实施了该方法,您会做-在falconwebtech.com/post/…上
viperguynaz 2013年


1
@RayLoveless您应该打电话给ModelState.IsValid-不要直接打电话给Validate
viperguynaz

63

我已经通过处理“ ModelState”解决了这一问题控制器包含字典。ModelState词典包含所有必须验证的成员。

解决方法如下:

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

...在某堂课...

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

[Required(ErrorMessage = "*")] 
public string DepentedProperty
{
  get;
  set;
}

...上课继续...

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

if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
   this.ModelState.Remove("DepentedProperty");
}

...

这样,我们就可以实现条件验证,而其他所有条件不变。


更新:

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

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


17
缺点是验证逻辑的一部分位于模型中,另一部分位于控制器中。
克里斯托弗·克莱斯

当然,这不是必需的。我仅显示最基本的示例。我已经使用模型上的接口和用于验证实现上述接口的模型的动作属性来实现了这一点。接口发出Validate(ModelStateDictionary modelState)方法。因此,最后您可以在模型中进行所有验证。无论如何,好点。
Peter Stegnar 2010年

在此之前,我喜欢这种方法的简单性,直到MVC团队立即开发出更好的东西。但是您的解决方案是否启用了客户端验证?
亚伦

2
@Aaron:很高兴您喜欢该解决方案,但是不幸的是,该解决方案不适用于客户端验证(因为每个验证属性都需要其JavaScript实现)。您可以使用“ Remote”属性来帮助自己,因此将仅发出Ajax调用以对其进行验证。
Peter Stegnar

您能扩展这个答案吗?这是有道理的,但我想确保自己对此有所了解。我面临着这种确切的情况,我想解决它。
理查德·B

36

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

条件:根据模型中其他属性的值,您要设置另一个属性。这是代码

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));


在asp.net mvc2中有任何等效的解决方案吗?ValidationResult,ValidationContext类在asp.net mvc2(.net framework 3.5)中不可用
User_MVC 2013年

2
如链接的博客所述,这仅在服务器
端起作用

2
我设法在MVC5的客户端上使它起作用,但是在客户端,无论DesiredValue是什么,它都会启动验证。
Geethanga 2014年

1
@Dan Hunex:在MVC4中,无论DesiredValue是什么,我都无法在客户端正常工作,并且它启动了验证。有什么帮助吗?
杰克

34

我一直在使用这个神奇的nuget,它可以动态注释ExpressiveAnnotations

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

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

3
ExpressiveAnnotation库是此处所有答案中最灵活,最通用的解决方案。感谢分享!
Sudhanshu Mishra'9

2
我一直在努力寻找解决方案,以度过美好的一天。ExpressiveAnnotations看起来很适合我!
Caverman's

ExpressiveAnnotation库很棒!
Doug Knudsen

1
它也有客户端支持!
Nattrass

1
但是,不支持.NET Core,而且看起来不会发生。
gosr



6

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

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

它以NuGet软件包的形式提供。


3

您需要在“人员”级别而不是“高级”级别进行验证,或者“高级”必须引用其父级“人员”。在我看来,您需要一种自验证机制,该机制定义对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));
        }
    }
}

“您需要在人员级别而不是高级级别进行验证”是的,这是一个选项,但是您会失去将错误附加到特定字段的能力,这是高级对象所必需的。
Peter Stegnar 2010年

3

我遇到了同样的问题,需要修改[Required]属性-依赖于http请求,使字段为必填。解决方案类似于Dan Hunex的答案,但是他的解决方案无法正常工作(请参阅注释)。我不使用非侵入式验证,仅使用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; }

对我而言,主要优势在于,无需像非侵入式验证那样编写自定义客户端验证器。它的作用与[Required]相同,但仅在您需要的情况下起作用。


关于扩展的部分DataAnnotationsModelValidator正是我需要看到的。谢谢。
TWIP


0

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

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

例:

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

在您的示例中,将所有内容保持不变,并在Controller的Action中添加建议的逻辑。我假设传递给控制器​​操作的ViewModel包含Person和Senior Person对象,这些对象中的数据是从UI中填充的。


0

我正在使用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”。然后,您只需要检查后操作的有效性即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.