Client-Side Validation
Client-side validation shows errors the moment a user leaves a field, before the form is even sent. In MVC 5 it costs you three script tags — the rules come from your Data Annotations.
On this page · 6 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Related tutorials
Learning objectives#
- Enable unobtrusive client-side validation
- Read the data-val attributes MVC generates
- Show messages with ValidationMessageFor and ValidationSummary
- Know why the server must still validate
Explanation#
Two app settings switch it on (they are true in new projects): ClientValidationEnabled and UnobtrusiveJavaScriptEnabled in Web.config.
Strongly typed helpers such as Html.TextBoxFor read the Data Annotations and emit data-val-* attributes: data-val-required="…", data-val-length-max="20", data-val-range-min="13".
jquery.validate.js and jquery.validate.unobtrusive.js (plus jQuery) read those attributes and validate as the user types and submits. Load them in the scripts section of forms that need them, usually through the ~/bundles/jqueryval bundle.
Html.ValidationMessageFor(m => m.Email) renders the span where a field’s message appears; Html.ValidationSummary() lists all errors at the top.
Client validation is only a convenience. Anyone can disable JavaScript or send a request with Postman, so the POST action must still check ModelState.IsValid.
Syntax#
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email)
@Html.ValidationMessageFor(m => m.Email)
@section scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Code example: The HTML that powers client-side validation#
// Models/SubscribeViewModel.cs
using System.ComponentModel.DataAnnotations;
namespace ShopSite.Models
{
public class SubscribeViewModel
{
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Enter a valid email")]
public string Email { get; set; }
[Range(13, 120, ErrorMessage = "Age must be 13 or over")]
public int Age { get; set; }
}
}
// Controllers/NewsletterController.cs
using System.Web.Mvc;
using ShopSite.Models;
namespace ShopSite.Controllers
{
public class NewsletterController : Controller
{
public ActionResult Subscribe() => View(new SubscribeViewModel { Age = 18 });
}
}
@* Views/Newsletter/Subscribe.cshtml *@
@model SubscribeViewModel
@using (Html.BeginForm())
{
@Html.LabelFor(m => m.Email)
@Html.TextBoxFor(m => m.Email)
@Html.ValidationMessageFor(m => m.Email)
@Html.LabelFor(m => m.Age)
@Html.TextBoxFor(m => m.Age)
@Html.ValidationMessageFor(m => m.Age)
<button type="submit">Subscribe</button>
}
@section scripts {
<script src="~/Scripts/jquery-3.7.1.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
}
GET /Newsletter/Subscribe
200 OK
<form action="/Newsletter/Subscribe" method="post"><label for="Email">Email</label><input data-val="true" data-val-email="Enter a valid email" data-val-required="Email is required" id="Email" name="Email" type="text" value="" /><span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span><label for="Age">Age</label><input data-val="true" data-val-number="The field Age must be a number." data-val-range="Age must be 13 or over" data-val-range-max="120" data-val-range-min="13" data-val-required="The Age field is required." id="Age" name="Age" type="text" value="18" /><span class="field-validation-valid" data-valmsg-for="Age" data-valmsg-replace="true"></span> <button type="submit">Subscribe</button>
</form>
<script src="/Scripts/jquery-3.7.1.min.js"></script>
<script src="/Scripts/jquery.validate.min.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js"></script>This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- TextBoxFor for Email adds data-val="true" plus one data-val-* attribute per rule: required and email, each with its message.
- Age is an int, so MVC adds data-val-number (must be a number) and data-val-required automatically, plus the range rule with min and max.
- ValidationMessageFor renders an empty span with data-valmsg-for; the unobtrusive script fills it with the message.
- The ~/ in the script src is resolved to the app’s root, so it also works when the site runs in a virtual directory.
- When the user leaves the Email box empty and tabs away, jQuery Validate shows “Email is required” without a round trip.
Real-world use case#
A checkout form used to post, wait two seconds and come back with “Phone is required”. Turning on unobtrusive validation shows the message instantly as the user leaves the field, and abandoned checkouts drop noticeably.
Common mistakes#
jquery.validate.unobtrusive needs jquery.validate, which needs jQuery. Loading them out of order gives “$.validator is undefined” and no validation.
<input name="Email"> has no data-val attributes, so the field is never validated in the browser. Use the For helpers.
Forms loaded with AJAX after the page loads are not validated until you call $.validator.unobtrusive.parse(form).
Best practices#
- Load the validation bundle only on pages with forms
- Always validate again on the server
- Use ValidationSummary(true) for model-level errors only
- Re-parse forms that are inserted with AJAX
Remember this#
Data Annotations → data-val-* → jQuery Validate.
Three scripts, in order: jQuery, validate, unobtrusive.
Client checks are for comfort; server checks are for safety.
Practice exercise#
Your task: Render a login form for a LoginViewModel with [Required] UserName and Password and show both validation message spans and a validation summary.
Need a hint?
Show solution
// Models/LoginViewModel.cs
using System.ComponentModel.DataAnnotations;
namespace ShopSite.Models
{
public class LoginViewModel
{
[Required] public string UserName { get; set; }
[Required, DataType(DataType.Password)] public string Password { get; set; }
}
}
// Controllers/LoginController.cs
using System.Web.Mvc;
using ShopSite.Models;
namespace ShopSite.Controllers
{
public class LoginController : Controller
{
public ActionResult Index() => View(new LoginViewModel());
}
}
@* Views/Login/Index.cshtml *@
@model LoginViewModel
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)
@Html.PasswordFor(m => m.Password)
@Html.ValidationMessageFor(m => m.Password)
}
GET /Login 200 OK <form action="/Login" method="post"><input data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /><span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span><input data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="password" /><span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></form>
Mini quiz#
Where do the data-val-* attributes come from?
Want more? Practise all ASP.NET MVC MCQs · ASP.NET MVC interview questions
My notes#
Saved in this browser.