Introduction
Validating user input has always been a challenging task for web developers. Not only do we want validation logic executing in the browser, but also we must validate the logic running on the server. The client-side logic gives users instant feedback on the information they entered into a web page and is an expected feature in today’s applications. Meanwhile, the server validation logic is in place because you should never trust information arriving from the network.

In this article, we will see how custom validation logic works in data annotations in MVC framework. We will see how we can implement our custom logic in data annotation attributes. If you are new to data annotation validation please read my first article for built in data annotation validation.
In this article, we will try to implement our custom validation logic for the address field in which the address cannot accept more than 50 words.
Overview
For this article, we created an application ASP.NET MVC application name DataAnnotationsValidations (you can download the source code for better understanding) and we are using Student Model Class that contains student relation information in which we are going to validate using Data Annotation.
- public class StudentModel
- {
- public Guid StudentId { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public DateTime DateOfBirth { get; set; }
- public string Address { get; set; }
- public string ContactNo { get; set; }
- public string EmailId { get; set; }
- public string ConfirmEmail { get; set; }
- public string UserName { get; set; }
- public string Password { get; set; }
- }
Student Controller
- using System.Web.Mvc;
- using DataAnnotationsValidations.Models;
- namespace DataAnnotationsValidations.Controllers
- {
- public class StudentController : Controller
- {
- // GET: Student
- public ActionResult Index()
- {
- return View();
- }
- // GET: Student/Create
- public ActionResult Create()
- {
- return View();
- }
- // POST: Student/Create
- [HttpPost]
- public ActionResult Create(StudentModel student)
- {
- try
- {
- if (ModelState.IsValid)
- {
- return RedirectToAction("Index");
- }
- return View();
- }
- catch
- {
- return View();
- }
- }
- }
- }

Create Student View
- @model DataAnnotationsValidations.Models.StudentModel
- @{
- ViewBag.Title = "Add Student";
- }
- <h3>Add New Student</h3>
- @using (Html.BeginForm())
- {
- @Html.AntiForgeryToken()
- <div class="form-horizontal">
- <hr />
- @Html.ValidationSummary(true, "", new { @class = "text-danger" })
- <div class="form-group">
- @Html.LabelFor(model => model.StudentId, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.StudentId, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.StudentId, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.DateOfBirth, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.DateOfBirth, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.DateOfBirth, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.Address, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.Address, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.Address, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.ContactNo, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.ContactNo, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.ContactNo, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.EmailId, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.EmailId, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.EmailId, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.ConfirmEmail, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.ConfirmEmail, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.ConfirmEmail, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- <div class="col-md-offset-2 col-md-10">
- <input type="submit" value="Create" class="btn btn-danger" />
- </div>
- </div>
- </div>
- }
Note
Data annotations are attributes we can find in the System.ComponentModel.DataAnnotations namespace. These attributes provide server-side validation and the framework also supports client-side validation.
Custom Annotations
Imagine we want to restrict the address field value of a student to limited number of words. For example, we might say 50 words is more than enough for an address field. You might also think that this type of validation (limiting a string to a maximum number of words) is something we can reuse with other modules in our application. If so, the validation logic is a candidate for packaging into reusable attributes.
So making the attributes reusable we are going to place all the custom annotation logic in one common place/folder, say Attributes, as we know all the validation annotations (such as Range, StringFormat ) ultimately derive from the ValidationAttribute base class. This base class is abstract and resides under System.ComponentModel.DataAnnotations namespace.
To keep validation logic in one place I have added an Attributes folder name in the application solution and added a class MaxWordAttributes class in it like this:
- using System.ComponentModel.DataAnnotations;
- namespace DataAnnotationsValidations.Attributes
- {
- public class MaxWordAttributes : ValidationAttribute
- {
- }
- }
- public class MaxWordAttributes : ValidationAttribute
- {
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
- {
- return ValidationResult.Success;
- }
- }
- public class MaxWordAttributes : ValidationAttribute
- {
- private readonly int _maxWords;
- public MaxWordAttributes(int maxWords)
- {
- _maxWords = maxWords;
- }
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
- {
- return ValidationResult.Success;
- }
- }
- public class MaxWordAttributes : ValidationAttribute
- {
- private readonly int _maxWords;
- public MaxWordAttributes(int maxWords)
- {
- _maxWords = maxWords;
- }
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
- {
- if (value == null) return ValidationResult.Success;
- var textValue = value.ToString();
- return textValue.Split(' ').Length > _maxWords
- ? new ValidationResult("Too long Address!")
- : ValidationResult.Success;
- }
- }
Here the problem with the last line new ValidationResult("Too long Address!") is we can see this is just a hardcoded error message. To write a good quality code we never want a hardcoded error message. We always want to pass the error message and that error message should display instated of this hardcoded one.
- public class MaxWordAttributes : ValidationAttribute
- {
- private readonly int _maxWords;
- public MaxWordAttributes(int maxWords)
- : base("{0} has to many words.")
- {
- _maxWords = maxWords;
- }
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
- {
- if (value == null) return ValidationResult.Success;
- var textValue = value.ToString();
- if (textValue.Split(' ').Length <= _maxWords) return ValidationResult.Success;
- var errorMessage = FormatErrorMessage((validationContext.DisplayName));
- return new ValidationResult(errorMessage);
- }
- }
- First we place the default error message to the base constructer. We should pull default error message from a resource file if we build an international stander application.
- Second notice how the default error message includes a parameter placeholder ({0}). The placeholder exists because the second change, the call inherited FormatErrorMessage method, will automatically format the string using the display name property.
FormatErrorMessage ensures that we use a correct error message string even if the string is localized into a resource file. We can use our custom annotation attributes in the following ways.
- [DataType(DataType.MultilineText)]
- [MaxWordAttributes(50)]
- public string Address { get; set; }
- [DataType(DataType.MultilineText)]
- [MaxWordAttributes(50,ErrorMessage="There are too many words in {0}.")]
- public string Address { get; set; }

Remote
The ASP.NET MVC framework adds an additional Remote validation attribute. This attribute is in System.Web.Mvc namespace.
The Remote attributes enable us to perform client-side validation with server callback. Take for example the UserName property is of a StudentModel, We are not going to allow the user name that already exists in our database. This means to say for every student there must be a unique UserName. To achieve this we need to use Remote attributes.
- [Remote("CheckUserName","Student")]
- public string UserName { get; set; }
- public JsonResult CheckUserName(string userName)
- {
- var studentUserList = new List<string> {"Manish", "Saurabh", "Akansha", "Ekta", "Rakesh", "Bhayia Jee"};
- var result = studentUserList.Any(userName.Contains);
- return Json(result, JsonRequestBehavior.AllowGet);
- }
Conclusion
In this article, we learned about the custom data annotation validation in ASP.Net MVC framework. If you have any questions or comments regarding this article, please post it in the comment section of this article. I hope you like it.

JroughgardenPosted Oct 25, 2018, 7:28 PM
Could you upload the code for this article, as you did for your first article? Thanks in advance.
Joe Gurria CelimendizPosted Sep 28, 2018, 7:51 AM
Yep, this is useful, thanks!
Chia-Chi YenPosted May 7, 2018, 2:04 AM
This client side validation is kind of like ajax and is way more easy and simple to use than implementing IClientValidatable, thank for sharing!
Ravi KandelPosted Jul 14, 2016, 11:59 AM
Thanks for sharing.
kalu singh raoPosted Jul 12, 2016, 1:49 AM
Nice...
Vignesh ManiPosted Jul 11, 2016, 5:34 PM
Nice