DotVVM - Build Conditional Validation Attribute

Validation in DotVVM

 
Validation in DotVVM is very similar to validation in ASP.Net MVC and ASP.Net Core.
 
You can validate your view model by using validation attributes. DotVVM does client side validation for only the following attributes:
  • Required
  • Compare
  • EmailAddress
  • Range
  • RegularExporession
If the user input is not valid you have two controls you can use to show error messages:
  • Validator Control
    Shows error message against the target input control

  • VaicationSummary Control
    Shows the summary for all errors in your view model.

Straight Validation

 
Suppose we have a small registration form that requires the user to enter his/her name, gender, and age. All fields are required. To validate user's input we just need to annotate our viewmodel properties with Required attribute. Our ViewModel class will be like this:
  1. [Required(ErrorMessage = "Name is required")]  
  2. public string Name { getset; }  
  3.   
  4. public string Gender { getset; }  
  5.   
  6. [Required(ErrorMessage = "Age is required")]  
  7. public int? Age { getset; }  
In our view we will use only Validator control to show error messages.
  1. <dot:Literal class="control-label col-md-3" Text="Name"></dot:Literal>  
  2.    <dot:TextBox Text="{value: Name}" class="form-control" Type="Normal"></dot:TextBox>  
  3.    <dot:Validator HideWhenValid="true" Value="{value: Name}" ShowErrorMessageText="true"></dot:Validator>  
  4.    <br /><br />  
  5.    <dot:Literal class="control-label col-md-3" Text="Gender"></dot:Literal>  
  6.    <dot:RadioButton CheckedItem="{value: Gender}" CheckedValue="{value: "1"}" Text="Male"></dot:RadioButton>  
  7.    <dot:RadioButton CheckedItem="{value: Gender}" CheckedValue="{value: "2"}" Text="Female"></dot:RadioButton>  
  8.    <br /><br />  
  9.    <dot:Literal class="control-label col-md-3" Text="Age"></dot:Literal>  
  10.    <dot:TextBox Text="{value: Age}" class="form-control" Type="Number"></dot:TextBox>  
  11.    <dot:Validator HideWhenValid="true" Value="{value: Age}" ShowErrorMessageText="true"></dot:Validator>  
  12.    <br /><br />  
  13. <dot:Button Click="{command: Save()}" Text="Save" class="btn btn-outline-secondary btn-sm"></dot:Button>  
For Validator control we set:
  • HideWhenValid to true to show error message only if the user's input is not valid
  • ShowErrorMessageText to true to show error message that comes from our ViewModel

Conditional Validation

 
We know women don't like to tell their real age so we want to respect this in our registration form by validating the age field only if the user selected male as his gender but if gender is female, we will not force her to enter her age.
 
In DotVVM there is no conditional Required validation, so we need to build our custom validation attribute.
 
What we actually need is just a new class that inherts ValidationAttribute class and overrides IsValid method with our logic.
  1. public class RequiredIfAttribute : ValidationAttribute  
  2. {  
  3.    public string PropertyName { getset; }  
  4.    public object Value { getset; }  
  5.   
  6.    public RequiredIfAttribute(string propertyName, object value, string errorMessage = "")  
  7.    {  
  8.       PropertyName = propertyName;  
  9.       ErrorMessage = errorMessage;  
  10.       Value = value;  
  11.    }  
  12.   
  13.    protected override ValidationResult IsValid(object value, ValidationContext validationContext)  
  14.    {  
  15.       var instance = validationContext.ObjectInstance;  
  16.       var type = instance.GetType();  
  17.       var proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);  
  18.       if (proprtyvalue.ToString() == Value.ToString() && value == null)  
  19.       {  
  20.          return new ValidationResult(ErrorMessage);  
  21.       }  
  22.       return ValidationResult.Success;  
  23.    }  
  24. }  
First we added a PropertyName property to check its value to start validation or skip it,  and Value property that will check against it.
 
Inside IsValid() method we are using reflection to get that value of the property and check if its value is equal to the provided value in the attribute. If the value of the current field is null then the field value is not valid otherwise the field is not required.
 
In our ViewModel we will replace Required attribute that decorates Age property with our new custom RequiredIf attribute .
  1. [RequiredIf(nameof(Gender), "1", ErrorMessage = "Age is required")]  
  2. public int? Age { getset; }  
Frist parameter is the PropertyName and the second paramter is the Value of the Gender that makes Age field required (I used 1 and 2 for simpilcity).
 
Restriction
 
Our custom validation is server side validation. DotVVM needs you to submit your form to be able to run your custom validation.
 

Summary

 
In this post we validated our ViewModel like we used to do in ASP.Net MVC or ASP.Net Core and also created our custom validation to validate fields based on another field value.
 
You can find the full source code on github.