views:

799

answers:

4

How to use data annotations to do a conditional validation on model?

For example, lets say we have the following model (Person and 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;
        }
    }

And the following view:

<%= 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)%>

I would like to be the "Senior.Description" property conditional required field based on the selection of the "IsSenior" propery (true -> required). How to implement conditional validation in ASP.NET MVC 2 with data annotations?


UPDATE

Found nice solution. Look below.

+3  A: 

You need to validate at Person level, not on Senior level, or Senior must have a reference to its parent Person. It seems to me that you need a self validation mechanism that defines the validation on the Person and not on one of its properties. I'm not sure, but I don't think DataAnnotations supports this out of the box. What you can do create your own Attribute that derives from ValidationAttribute that can be decorated on class level and next create a custom validator that also allows those class-level validators to run.

I know Validation Application Block supports self-validation out-of the box, but VAB has a pretty steep learning curve. Nevertheless, here's an example using 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));
        }
    }
}
Steven
"You need to validate at Person level, not on Senior level" Yes this is an option, but you loose the ability that the error is appended to particular field, that is required in the Senior object.
Peter Stegnar
+3  A: 

I have solved it with handling the "ModelState" dictionary which is contained by the controller. ModelState dictionary include all the members that are have to be validated.

Here is the solution:

If you need to implement a conditional validation based on some field (e.g. if A=true, then B is requited), while maintain property level error message (this is not true for the custom validators that are on object level) you can achieve this by handling "ModelState" by simply remove unwanted validations from it.

...In some class...

public bool PropertyThatRequiredAnotherFieldToBeFilled
{
  get;
  set;
}

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

...class continues...

...In some controller action ...

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

...

By this we achieve conditional validation, while leaving everything else the same.


UPDATE:

My final implementation look like that I have implemented it with an interface on model and action attribute that validates model which implements the mentioned interface. Interface prescribes the Validate(ModelStateDictionary modelState) method. Attribute on action just call the Validate(modelState) on IValidatiorSomething.

I did not want to complicate this answer, that way I did not mention the final implementation details, with at the end in production code matters.

Peter Stegnar
The downside is that one of part your validation logic is located in the model and the other part in the controller(s).
Kristof Claes
Well of course this is not necessary. I just show the most basic example. I have implemented this with interface on model and with action attribute that validates model which implements the mentioned interface. Interface perspires the Validate(ModelStateDictionary modelState) method. So finally you DO all validation in the model. Anyway, good point.
Peter Stegnar
+1  A: 

You can disable validators conditionally by removing errors from ModelState:

ModelState["DependentProperty"].Errors.Clear();
Pavel Chuchuva
A: 

Check out this guy:

http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx

I am working through his example project right now.

Merritt