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)
- {
-
- var containerType = validationContext.ObjectInstance.GetType();
- var field = containerType.GetProperty(this.DependentProperty);
- if (field != null)
- {
-
- var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
-
-
- if ((dependentvalue == null && this.TargetValue == null) || (dependentvalue != null && dependentvalue.Equals(this.TargetValue)))
- {
-
- if (!InnerAttribute.IsValid(value))
- {
-
- 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);
-
- string targetValue = (this.TargetValue ?? "").ToString();
- if (this.TargetValue.GetType() == typeof(bool))
- {
- targetValue = targetValue.ToLower();
- }
- rule.ValidationParameters.Add("dependentproperty", depProp);
- rule.ValidationParameters.Add("targetvalue", targetValue);
-
- 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);
-
- 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()
- {
-
- 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
- {
-
- public ActionResult Index()
- {
- PersonalDetail model = new PersonalDetail();
-
- return View(model);
- }
- [HttpPost]
- public ActionResult PersonalDetail(PersonalDetail model)
- {
-
- if (ModelState.IsValid)
- {
-
-
- 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.
Step 8
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.