How To Add Custom Validator For Any Model In C#

In this article, we will discuss how to add a custom validator for any model in C#. I have tried my best many times to explain the use of the custom validator needed while working with models in C#. I hope this would be helpful in the situations where we need to set the validations for some of the properties of a model class based on some condition.

Data Annotations

DataAnnotations are used in MVC to validate the data. It works on Client-side as well as server side. There are some pre-defined data-annotation attributes which we use directly to validate the data, like - [Required], [MaxLength], [MinLength] etc but sometimes there might be a situation where you want these validators to validate the field depending upon the value of another field. In that case, we have to use custom validators.

Requirement

There might be a situation where you have a model named "PersonalDetail" and you want to validate some of the data based on some condition. Like "PANCars" and "Salary" must be required if the person is Employee otherwise these fields should not be required. Also, Salary range should be validated conditionally.

So, in this case, if we will use [Required] attribute for both “PANCard” and “Salary”, these fields will be treated as mandatory every time. But, we want to make them required conditionally I.e. dependent on the value of “IsEmployee” field.

Here, are the steps to be followed,

Step 1

Create a New MVC4 or above application. Skip this step if want to use an existing project. 

Step 2

Create a new class named “CustomValidators.cs”. In this, I have defined custom validators for Required as well as Range and named those “RequiredIf” and “RangeIf”. I have created this class under “App_Start” folder, you may create it in any folder according to your convenience and requirement.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel.DataAnnotations;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using System.Web.Mvc;  
  7.   
  8. namespace WebApplication1.App_Start  
  9. {  
  10.     public class CustomValidators  
  11.     {  
  12.   
  13.         [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]  
  14.         public abstract class ConditionalValidationAttribute : ValidationAttribute, IClientValidatable  
  15.         {  
  16.             protected readonly ValidationAttribute InnerAttribute;  
  17.             public string DependentProperty { get; set; }  
  18.             public object TargetValue { get; set; }  
  19.             protected abstract string ValidationName { get; }  
  20.   
  21.             protected virtual IDictionary<string, object> GetExtraValidationParameters()  
  22.             {  
  23.                 return new Dictionary<string, object>();  
  24.             }  
  25.   
  26.             protected ConditionalValidationAttribute(ValidationAttribute innerAttribute, string dependentProperty, object targetValue)  
  27.             {  
  28.                 this.InnerAttribute = innerAttribute;  
  29.                 this.DependentProperty = dependentProperty;  
  30.                 this.TargetValue = targetValue;  
  31.             }  
  32.   
  33.             protected override ValidationResult IsValid(object value, ValidationContext validationContext)  
  34.             {  
  35.                 // get a reference to the property this validation depends upon    
  36.                 var containerType = validationContext.ObjectInstance.GetType();  
  37.                 var field = containerType.GetProperty(this.DependentProperty);  
  38.                 if (field != null)  
  39.                 {  
  40.                     // get the value of the dependent property    
  41.                     var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);  
  42.   
  43.                     // compare the value against the target value    
  44.                     if ((dependentvalue == null && this.TargetValue == null) || (dependentvalue != null && dependentvalue.Equals(this.TargetValue)))  
  45.                     {  
  46.                         // match => means we should try validating this field    
  47.                         if (!InnerAttribute.IsValid(value))  
  48.                         {  
  49.                             // validation failed - return an error    
  50.                             return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });  
  51.                         }  
  52.                     }  
  53.                 }  
  54.                 return ValidationResult.Success;  
  55.             }  
  56.   
  57.             public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)  
  58.             {  
  59.                 var rule = new ModelClientValidationRule()  
  60.                 {  
  61.                     ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),  
  62.                     ValidationType = ValidationName,  
  63.                 };  
  64.                 string depProp = BuildDependentPropertyId(metadata, context as ViewContext);  
  65.                 // find the value on the control we depend on; if it's a bool, format it javascript style    
  66.                 string targetValue = (this.TargetValue ?? "").ToString();  
  67.                 if (this.TargetValue.GetType() == typeof(bool))  
  68.                 {  
  69.                     targetValue = targetValue.ToLower();  
  70.                 }  
  71.                 rule.ValidationParameters.Add("dependentproperty", depProp);  
  72.                 rule.ValidationParameters.Add("targetvalue", targetValue);  
  73.                 // Add the extra params, if any    
  74.                 foreach (var param in GetExtraValidationParameters())  
  75.                 {  
  76.                     rule.ValidationParameters.Add(param);  
  77.                 }  
  78.                 yield return rule;  
  79.             }  
  80.   
  81.             private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)  
  82.             {  
  83.                 string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);  
  84.                 // This will have the name of the current field appended to the beginning, because the TemplateInfo's context has had this fieldname appended to it.    
  85.                 var thisField = metadata.PropertyName + "_";  
  86.                 if (depProp.StartsWith(thisField))  
  87.                 {  
  88.                     depProp = depProp.Substring(thisField.Length);  
  89.                 }  
  90.                 return depProp;  
  91.             }  
  92.         }  
  93.         public class RequiredIfAttribute : ConditionalValidationAttribute  
  94.         {  
  95.             protected override string ValidationName  
  96.             {  
  97.                 get { return "requiredif"; }  
  98.             }  
  99.             public RequiredIfAttribute(string dependentProperty, object targetValue)  
  100.                 : base(new RequiredAttribute(), dependentProperty, targetValue)  
  101.             {  
  102.             }  
  103.             protected override IDictionary<string, object> GetExtraValidationParameters()  
  104.             {  
  105.                 return new Dictionary<string, object>  
  106.         {  
  107.             { "rule""required" }  
  108.         };  
  109.             }  
  110.         }  
  111.         public class RangeIfAttribute : ConditionalValidationAttribute  
  112.         {  
  113.             private readonly int minimum;  
  114.             private readonly int maximum;  
  115.             protected override string ValidationName  
  116.             {  
  117.                 get { return "rangeif"; }  
  118.             }  
  119.             public RangeIfAttribute(int minimum, int maximum, string dependentProperty, object targetValue)  
  120.                 : base(new RangeAttribute(minimum, maximum), dependentProperty, targetValue)  
  121.             {  
  122.                 this.minimum = minimum;  
  123.                 this.maximum = maximum;  
  124.             }  
  125.             protected override IDictionary<string, object> GetExtraValidationParameters()  
  126.             {  
  127.                 // Set the rule Range and the rule param [minumum,maximum]    
  128.                 return new Dictionary<string, object>  
  129.         {  
  130.             {"rule""range"},  
  131.             { "ruleparam", string.Format("[{0},{1}]"this.minimum, this.maximum) }  
  132.         };  
  133.             }  
  134.         }  
  135.     }  
  136. }  

Step 3

Right-click on the Model Folder then select Add New Item -> Add New Class -> PersonalDetail.cs. In this file, you can see that I have used RequiredIf and RangeIf attributes. Both of these attributes have the first parameter as a dependent field and second parameter value of a dependent attribute for which these attributes will work. In our case, dependent property id “ISEmployee” and its value for which validator should work is “true”.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel.DataAnnotations;  
  4. using System.Linq;  
  5. using System.Web;  
  6. using static WebApplication1.App_Start.CustomValidators;  
  7.   
  8. namespace WebApplication1.Models  
  9. {  
  10.       
  11.     public class PersonalDetail  
  12.     {  
  13.           
  14.         [Required(ErrorMessage = "Please enter Name")]  
  15.         [Display(Name="Name")]  
  16.         public string Name { get; set; }  
  17.         [Display(Name = "IsEmployee")]  
  18.         public bool IsEmployee { get; set; }  
  19.         [Display(Name = "PANCard")]  
  20.         [RequiredIf("IsEmployee"true,ErrorMessage = "PANCard is required for Employee")]  
  21.         public string PANCard { get; set; }  
  22.         [Display(Name = "Salary")]  
  23.         [RequiredIf("IsEmployee"true, ErrorMessage = "Salary is required for Employee"), RangeIf(500, 10000, "IsEmployee"true, ErrorMessage = "Salary range is 500 - 10000")]  
  24.         public decimal? Salary { get; set; }  
  25.     }  
  26. }  

Step 4

Now, create a new controller named “PersonalDetailController”. Skip this if want to use for any existing code.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using System.Web.Mvc;  
  6. using WebApplication1.Models;  
  7.   
  8. namespace WebApplication1.Controllers  
  9. {  
  10.     public class PersonalDetailController : Controller  
  11.     {  
  12.         // GET: PersonalDetail  
  13.         public ActionResult Index()  
  14.         {  
  15.             PersonalDetail model = new PersonalDetail();  
  16.   
  17.             return View(model);  
  18.         }  
  19.         [HttpPost]  
  20.         public ActionResult PersonalDetail(PersonalDetail model)  
  21.         {  
  22.   
  23.             if (ModelState.IsValid)  
  24.             {  
  25.                 // write save code here  
  26.   
  27.                 return RedirectToAction("Index");  
  28.             }  
  29.             else  
  30.             {  
  31.                 return View("Index",model);  
  32.             }  
  33.         }  
  34.     }  
  35. }  

Step 5

Create Index.cshtml for the “Index” action of controller “PersonalDetail”

  1. @model WebApplication1.Models.PersonalDetail  
  2. @{  
  3.     ViewBag.Title = "PersonalDetail";  
  4.     Layout = "~/Views/Shared/_Layout.cshtml";  
  5. }  
  6.   
  7. <h2>PersonalDetail</h2>  
  8. @using (Html.BeginForm("PersonalDetail""PersonalDetail", FormMethod.Post, new { @class = "my_form" }))  
  9. {  
  10.     <table>  
  11.         <tr>  
  12.             <td>  @Html.LabelFor(x => x.Name)</td>  
  13.             <td>@Html.TextBoxFor(x => x.Name)</td>  
  14.             <td>@Html.ValidationMessageFor(x => x.Name)</td>  
  15.         </tr>  
  16.         <tr>  
  17.             <td>@Html.LabelFor(x => x.IsEmployee)</td>  
  18.   
  19.             <td>@Html.CheckBoxFor(x => x.IsEmployee)</td>  
  20.             <td>@Html.ValidationMessageFor(x => x.IsEmployee)</td>  
  21.         </tr>  
  22.         <tr>  
  23.             <td>  
  24.                 @Html.LabelFor(x => x.PANCard)  
  25.             </td>  
  26.             <td>@Html.TextBoxFor(x => x.PANCard)</td>  
  27.             <td>@Html.ValidationMessageFor(x => x.PANCard)</td>  
  28.         </tr>  
  29.         <tr>  
  30.             <td>  
  31.                 @Html.LabelFor(x => x.Salary)  
  32.             </td>  
  33.             <td>@Html.TextBoxFor(x => x.Salary)</td>  
  34.             <td>@Html.ValidationMessageFor(x => x.Salary)</td>  
  35.         </tr>  
  36.         <tr>  
  37.             <td colspan="3">  
  38.                 <input type="submit" value="Save" />  
  39.             </td>  
  40.         </tr>  
  41.     </table>  
  42. }  
  43. <style>  
  44.     .field-validation-error {  
  45.         color: red;  
  46.         font-weight: bold;  
  47.     }  
  48. </style>  

Step 6

Run the project and go for URL: http://localhost:62564/PersonalDetail/Index

C#

Depending upon input it will validate the fields, 

Step 7

If “IsEmployee” checkbox is unchecked, “PANCard” and “Salary” will not be validated but the name will be validated as it has [Required] attribute.

C#

Step 8

If “ISEmployee” is checked and both “PANCard” as well as “Salary” are blank.

C#

Step 9

If “ISEmployee” is checked and the salary is less than 500.

C#