ASP.NET MVC Conditional validation

159,805

Solution 1

I have solved this by handling the "ModelState" dictionary, which is contained by the controller. The ModelState dictionary includes all the members that 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 required), while maintaining property level error messaging (this is not true for the custom validators that are on object level) you can achieve this by handling "ModelState", by simply removing 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");
}

...

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


UPDATE:

This is my final implementation: I have used an interface on the model and the action attribute that validates the model which implements the said interface. Interface prescribes the Validate(ModelStateDictionary modelState) method. The attribute on action just calls the Validate(modelState) on IValidatorSomething.

I did not want to complicate this answer, so I did not mention the final implementation details (which, at the end, matter in production code).

Solution 2

There's a much better way to add conditional validation rules in MVC3; have your model inherit IValidatableObject and implement the Validate method:

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.");
    }
}

Read more at Introducing ASP.NET MVC 3 (Preview 1).

Solution 3

I had the same problem yesterday but I did it in a very clean way which works for both client side and server side validation.

Condition: Based on the value of other property in the model, you want to make another property required. Here is the code

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

Here PropertyName is the property on which you want to make your condition DesiredValue is the particular value of the PropertyName (property) for which your other property has to be validated for required

Say you have the following

public class User
{
    public UserType UserType { get; set; }

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

At last but not the least , register adapter for your attribute so that it can do client side validation (I put it in global.asax, Application_Start)

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

Solution 4

I've been using this amazing nuget that does dynamic annotations ExpressiveAnnotations

You could validate any logic you can dream of:

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

Solution 5

You can disable validators conditionally by removing errors from ModelState:

ModelState["DependentProperty"].Errors.Clear();
Share:
159,805
Peter Stegnar
Author by

Peter Stegnar

Software developer, technology enthusiast, specialist in .NET, JavaScript, Angular and Cloud. Also really interested in quantum finance.

Updated on January 25, 2020

Comments

  • Peter Stegnar
    Peter Stegnar over 4 years

    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?

    • Darin Dimitrov
      Darin Dimitrov over 14 years
      I've recently asked similar question: stackoverflow.com/questions/2280539/…
    • Steven
      Steven over 14 years
      I'm confused. A Senior object is always a senior, so why can IsSenior be false in that case. Don't you just need the 'Person.Senior' property to be null when Person.IsSenior is false. Or why not implement the IsSenior property as follows: bool IsSenior { get { return this.Senior != null; } }.
    • Peter Stegnar
      Peter Stegnar over 14 years
      Steven: "IsSenior" translates to the checkbox field in the view. When user checks the "IsSenior" checkBox then the "Senior.Description" Field become mandatory.
    • Peter Stegnar
      Peter Stegnar over 14 years
      Darin Dimitrov: Well sort of, but not quite. You see, how would you achieve that the the error mesage is appent to the specific field? If you validate at object level, you get an error at object level. I need error on property level.
  • Peter Stegnar
    Peter Stegnar over 14 years
    "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.
  • Kristof Claes
    Kristof Claes over 14 years
    The downside is that one of part your validation logic is located in the model and the other part in the controller(s).
  • Peter Stegnar
    Peter Stegnar over 14 years
    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.
  • Aaron
    Aaron over 13 years
    I like the simplicity of this approach in the mean time until the MVC team builds something better out of the box. But does your solution work with client side validation enabled??
  • Peter Stegnar
    Peter Stegnar over 13 years
    @Aaron: I am happy that you like solution, but unfortunately this solution does not work with client side validation (as every validation attribute need its JavaScript implementation). You could help yourself with "Remote" attribute, so just Ajax call will be emitted to validate it.
  • Richard B
    Richard B about 13 years
    Are you able to expand on this answer? This makes some sense, but I want to make sure I'm crystal on it. I'm faced with this exact situation, and I want to get it resolved.
  • Jeyhun Rahimov
    Jeyhun Rahimov over 11 years
    if property is "int" type, that requires value, if fill that field, Validate does not work..
  • Dan Hunex
    Dan Hunex about 11 years
    This is was the original starting point miroprocessordev.blogspot.com/2012/08/…
  • Peter Stegnar
    Peter Stegnar almost 11 years
    @Levitikon: Sure it does not, as any other custom validation implementation only on server side. There is also possibility to implement this validation that is supported on client side.
  • NightOwl888
    NightOwl888 almost 11 years
    Unfortunately, Microsoft put this in the wrong layer - validation is business logic and this interface is in the System.Web DLL. In order to use this, you have to give your business layer a dependency on a presentation technology.
  • viperguynaz
    viperguynaz almost 11 years
    you do if you implement it - see full example at falconwebtech.com/post/…
  • User_MVC
    User_MVC over 10 years
    Is there any equvalent solution in asp.net mvc2? ValidationResult, ValidationContext classes are not available in asp.net mvc2 (.net framework 3.5)
  • User_MVC
    User_MVC over 10 years
    Is there any equvalent solution in asp.net mvc2? IValidatableObject inter face & ValidationResult, ValidationContext classes are not available in asp.net mvc2 (.net framework 3.5)
  • Dan Hunex
    Dan Hunex over 10 years
    @User_MVC I didnt work on mvc2 and dont know but if there is any validation , try to extend that
  • Pakman
    Pakman over 10 years
    This only works server-side as the linked blog states
  • Geethanga
    Geethanga over 10 years
    I managed to get this working in the client side with MVC5, but in client it fires up the validation no matter what the DesiredValue is.
  • Jack
    Jack over 9 years
    @Dan Hunex : In MVC4, I have not managed to work properly on client side and it fires up the validation no matter what the DesiredValue is. Any help pls?
  • twip
    twip over 8 years
    The part about extending DataAnnotationsModelValidator was exactly what I needed to see. Thank you.
  • Ammar Khan
    Ammar Khan over 8 years
    @PeterStegnar will you please provide an example through an interface. I am more interested in to know how this could done with an interface.
  • Peter Stegnar
    Peter Stegnar over 8 years
    @AmmarKhan What do you mean thru interface? Usually you do not use interface in a MVC model ... Or what do you mean? Interface like UI?
  • Yulian
    Yulian about 8 years
    How to make client side validation work: stackoverflow.com/a/15975880/2419808
  • Sudhanshu Mishra
    Sudhanshu Mishra almost 8 years
    The ExpressiveAnnotation library is the most flexible and generic solution of all answers here. Thanks for sharing!
  • Caverman
    Caverman about 7 years
    I've been banging my head trying to find a solution for a solid day. ExpressiveAnnotations looks to be the fix for me!
  • John Adam
    John Adam about 7 years
    What is ErrorMessageResourceType ? I am passing null I am getting error.
  • Smit Patel
    Smit Patel about 7 years
    falconwebtech.com/post/… - @viperguynaz this isn't working
  • Doug Knudsen
    Doug Knudsen almost 7 years
    ExpressiveAnnotation library is awesome!
  • Nattrass
    Nattrass almost 7 years
    It has client side support too!
  • gosr
    gosr almost 7 years
    No support for .NET Core though, and doesn't look like it'll happen.
  • RayLoveless
    RayLoveless over 6 years
    This isn't working. How do you pass in the validationContext when calling myViewmodel.Validate(..)? Does Validate need to be called manually?
  • viperguynaz
    viperguynaz over 6 years
    @RayLoveless you should be calling ModelState.IsValid - not calling Validate directly
  • RayLoveless
    RayLoveless over 6 years
    @viperguynaz thanks it's calling validate now but the validateMessageFor(m => m.myField) isn't showing the error message. How does the error message get bound to the specific viewModel's property? Thanks for your help.
  • viperguynaz
    viperguynaz over 6 years
    You need to add a Validatio message to your view - Html.ValidationMessage("SomProperty", "*")
  • Bil Simser
    Bil Simser over 5 years
    IMHO this should be marked as the correct answer. While you could build a custom attribute and all that, you're leaking something into the system that isn't necessary (unless you want to validate multiple models conditionally). This is the cleanest way and will be called in the controller when you (normally) do a if(ModelState.IsValid) call before saving/processing/etc.
  • xhafan
    xhafan almost 4 years
    .NET Core clone of ExpressiveAnnotations which works for me: github.com/uon-nuget/UoN.ExpressiveAnnotations.NetCore
  • user3280560
    user3280560 over 3 years
    I know this an older thread, but for completion - @viperguynaz - to bind the error to a specific property (e.g. for client side model binding validation error displaying), use the overloaded method instead. "yield return new ValidationResult("ErrorMessage.", new[] {"PutNameOfPropertyHere"}); Without this, the validation error is generic, and would require a catch-all warning label within the view, to be displayed.