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.
- using System;
- using System.Collections.Generic;
- using System.ComponentModel.DataAnnotations;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- namespace WebApplication1.App_Start
- {
- public class CustomValidators
- {
- [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
- public abstract class ConditionalValidationAttribute : ValidationAttribute, IClientValidatable
- {
- protected readonly ValidationAttribute InnerAttribute;
- public string DependentProperty { get; set; }
- public object TargetValue { get; set; }
- protected abstract string ValidationName { get; }
- protected virtual IDictionary<string, object> GetExtraValidationParameters()
- {
- return new Dictionary<string, object>();
- }
- protected ConditionalValidationAttribute(ValidationAttribute innerAttribute, string dependentProperty, object targetValue)
- {
- this.InnerAttribute = innerAttribute;
- this.DependentProperty = dependentProperty;
- this.TargetValue = targetValue;
- }
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
- {
- // get a reference to the property this validation depends upon
- var containerType = validationContext.ObjectInstance.GetType();
- var field = containerType.GetProperty(this.DependentProperty);
- if (field != null)
- {
- // get the value of the dependent property
- var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
- // compare the value against the target value
- if ((dependentvalue == null && this.TargetValue == null) || (dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
- {
- // match => means we should try validating this field
- if (!InnerAttribute.IsValid(value))
- {
- // validation failed - return an error
- return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
- }
- }
- }
- return ValidationResult.Success;
- }
- public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
- {
- var rule = new ModelClientValidationRule()
- {
- ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
- ValidationType = ValidationName,
- };
- string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
- // find the value on the control we depend on; if it's a bool, format it javascript style
- string targetValue = (this.TargetValue ?? "").ToString();
- if (this.TargetValue.GetType() == typeof(bool))
- {
- targetValue = targetValue.ToLower();
- }
- rule.ValidationParameters.Add("dependentproperty", depProp);
- rule.ValidationParameters.Add("targetvalue", targetValue);
- // Add the extra params, if any
- foreach (var param in GetExtraValidationParameters())
- {
- rule.ValidationParameters.Add(param);
- }
- yield return rule;
- }
- private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
- {
- string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty);
- // 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.
- var thisField = metadata.PropertyName + "_";
- if (depProp.StartsWith(thisField))
- {
- depProp = depProp.Substring(thisField.Length);
- }
- return depProp;
- }
- }
- public class RequiredIfAttribute : ConditionalValidationAttribute
- {
- protected override string ValidationName
- {
- get { return "requiredif"; }
- }
- public RequiredIfAttribute(string dependentProperty, object targetValue)
- : base(new RequiredAttribute(), dependentProperty, targetValue)
- {
- }
- protected override IDictionary<string, object> GetExtraValidationParameters()
- {
- return new Dictionary<string, object>
- {
- { "rule", "required" }
- };
- }
- }
- public class RangeIfAttribute : ConditionalValidationAttribute
- {
- private readonly int minimum;
- private readonly int maximum;
- protected override string ValidationName
- {
- get { return "rangeif"; }
- }
- public RangeIfAttribute(int minimum, int maximum, string dependentProperty, object targetValue)
- : base(new RangeAttribute(minimum, maximum), dependentProperty, targetValue)
- {
- this.minimum = minimum;
- this.maximum = maximum;
- }
- protected override IDictionary<string, object> GetExtraValidationParameters()
- {
- // Set the rule Range and the rule param [minumum,maximum]
- return new Dictionary<string, object>
- {
- {"rule", "range"},
- { "ruleparam", string.Format("[{0},{1}]", this.minimum, this.maximum) }
- };
- }
- }
- }
- }
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”.
- using System;
- using System.Collections.Generic;
- using System.ComponentModel.DataAnnotations;
- using System.Linq;
- using System.Web;
- using static WebApplication1.App_Start.CustomValidators;
- namespace WebApplication1.Models
- {
- public class PersonalDetail
- {
- [Required(ErrorMessage = "Please enter Name")]
- [Display(Name="Name")]
- public string Name { get; set; }
- [Display(Name = "IsEmployee")]
- public bool IsEmployee { get; set; }
- [Display(Name = "PANCard")]
- [RequiredIf("IsEmployee", true,ErrorMessage = "PANCard is required for Employee")]
- public string PANCard { get; set; }
- [Display(Name = "Salary")]
- [RequiredIf("IsEmployee", true, ErrorMessage = "Salary is required for Employee"), RangeIf(500, 10000, "IsEmployee", true, ErrorMessage = "Salary range is 500 - 10000")]
- public decimal? Salary { get; set; }
- }
- }
Step 4
Now, create a new controller named “PersonalDetailController”. Skip this if want to use for any existing code.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
- using WebApplication1.Models;
- namespace WebApplication1.Controllers
- {
- public class PersonalDetailController : Controller
- {
- // GET: PersonalDetail
- public ActionResult Index()
- {
- PersonalDetail model = new PersonalDetail();
- return View(model);
- }
- [HttpPost]
- public ActionResult PersonalDetail(PersonalDetail model)
- {
- if (ModelState.IsValid)
- {
- // write save code here
- return RedirectToAction("Index");
- }
- else
- {
- return View("Index",model);
- }
- }
- }
- }
Step 5
Create Index.cshtml for the “Index” action of controller “PersonalDetail”
- @model WebApplication1.Models.PersonalDetail
- @{
- ViewBag.Title = "PersonalDetail";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
- <h2>PersonalDetail</h2>
- @using (Html.BeginForm("PersonalDetail", "PersonalDetail", FormMethod.Post, new { @class = "my_form" }))
- {
- <table>
- <tr>
- <td> @Html.LabelFor(x => x.Name)</td>
- <td>@Html.TextBoxFor(x => x.Name)</td>
- <td>@Html.ValidationMessageFor(x => x.Name)</td>
- </tr>
- <tr>
- <td>@Html.LabelFor(x => x.IsEmployee)</td>
- <td>@Html.CheckBoxFor(x => x.IsEmployee)</td>
- <td>@Html.ValidationMessageFor(x => x.IsEmployee)</td>
- </tr>
- <tr>
- <td>
- @Html.LabelFor(x => x.PANCard)
- </td>
- <td>@Html.TextBoxFor(x => x.PANCard)</td>
- <td>@Html.ValidationMessageFor(x => x.PANCard)</td>
- </tr>
- <tr>
- <td>
- @Html.LabelFor(x => x.Salary)
- </td>
- <td>@Html.TextBoxFor(x => x.Salary)</td>
- <td>@Html.ValidationMessageFor(x => x.Salary)</td>
- </tr>
- <tr>
- <td colspan="3">
- <input type="submit" value="Save" />
- </td>
- </tr>
- </table>
- }
- <style>
- .field-validation-error {
- color: red;
- font-weight: bold;
- }
- </style>
Step 6
Run the project and go for URL: http://localhost:62564/PersonalDetail/Index

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.

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

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


Rajnish KumarPosted Feb 16, 2021, 9:25 AM
Mam, Validation working properly, but validation has been done with page "PostBack", which is not good for validation, Please help us for how to stop the page postback
Nripendra OjhaPosted Sep 12, 2019, 6:45 AM
Mam, It is not working in MVC Core 2.2. May you post another article or code for MVC Core
Niveen SaadehPosted Jun 3, 2019, 11:40 AM
Thank you for the great article. I have a problem. Error messages don't show in @Html.ValidationMessageFor. I see errors in modelstate, but they don't go to view. please help me.
Packiaraj SanthiyaguPosted Jul 30, 2018, 8:00 AM
Nice blog. Really needed one .
Farhan AhmedPosted Jul 24, 2018, 12:14 AM
Nice.. it helpful.
Mohit KalaPosted Jul 22, 2018, 11:01 PM
Nice Article Mam
Humayun Kabir MamunPosted Jul 22, 2018, 6:33 AM
Thanks Ruchi for this helpful article...
Abhishek KumarPosted Jul 21, 2018, 10:27 AM
Nice article..
Suraj KumarPosted Jul 21, 2018, 3:22 AM
Very Useful as Validation is important everywhere.
Jignesh KumarPosted Jul 21, 2018, 12:05 AM
Nice explanation and useful in realtime scenairo.. Thanks
Hadshana KamalanathanPosted Jul 20, 2018, 10:39 AM
Good one..