The MVC client-validation features are built on top of the jQuery Validation library and, if you prefer, you can use the Validation library directly and ignore the MVC features. The Validation library is very flexible and feature-rich – it is well worth exploring, if only to understand how to customize the MVC features to take best advantage of the available validation options. You do have to have some familiarity with JavaScript to use the validation library, however. This is a simple example of the kind of script that is required:
$(document).ready(function () { $('form').validate({
errorLabelContainer: '#validtionSummary', wrapper: 'li',
rules: {
ClientName: { required: true, }
},
messages: {
ClientName: "Please enter your name"
} });
});
The MVC client-validation features hide away the JavaScript – and they have the advantage of taking effect for both client- and server-side validation. Either approach can be used in an MVC application – although you should be careful if tempted to mix and match approaches in a single view, as there can be some unfortunate interactions. For details of how to use the validation library see http://bassistance.de/jquery-plugins.
Customizing Client-Side Validation
The built-in support for client-side validation is nice, but it is limited to the six attributes that we described in Table 3, earlier in the chapter. The jQuery Validation library supports some more complex validation rules – and fortunately, the MVC unobtrusive validation library lets us take advantage of them with just a little extra work.
Explicitly Creating Validation HTML Attributes
The most direct way to take advantage of the additional validation rules is to manually generate the required attributes in the view, as shown in Listing 25.
Listing 25. Manually generating the validation HTML attributes
@model MvcApp.Models.Appointment
@{
ViewBag.Title = "Make A Booking";
}
<h4>Book an Appointment</h4>
@using (Html.BeginForm()) {
@Html.ValidationSummary()
<p>Your name:
@Html.TextBoxFor(m => m.ClientName, new { data_val = "true", data_val_email = "Enter a valid email address",
data_val_required = "Please enter your name"}) @Html.ValidationMessageFor(m => m.ClientName)</p>
<p>Appointment Date: @Html.EditorFor(m => m.Date) @Html.ValidationMessageFor(m => m.Date)</p>
<p>@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions @Html.ValidationMessageFor(m => m.TermsAccepted)</p>
<input type="submit" value="Make Booking" />
}
We can’t use the templated helpers to generate an editor for a property if we also want to provide additional attributes, so we have used the Html.TextBoxFor helper instead and used the version which accepts an anonymous type to use for HTML attributes.
Tip The segments of the HTML attributes names are separated by hyphens (-), but this is an illegal character for C# variable names. To work around this, we specify the attribute names using underscores (_), which are automatically converted to hyphens when the HTML is generated.
When we render the view, we generate the following HTML for the ClientName property:
<input data-val="true" data-val-email="Enter a valid email address"
data-val-required="Please enter your name" id="ClientName"
name="ClientName" type="text" value="" />
The required rule is the same one that using the Required attribute generates. The email rule ensures that if there is a value entered into the field, it is in the format of a valid email address. Table 5 describes the set of validation rules that we can use.
Table 5. Useful jQuery Validation Rules Validation
Rule
Validation Attribute
Description
Required Required Requires a value to be entered – this is the rule that the Required validation attribute uses.
Length StringLength A value must have a minimum and/or maximum number of
characters. The minimum length is specified by the data-val- length-min attribute and the maximum length by the data-val- length-max attribute. We can provide just the –min or –max attribute if we want to limit only one aspect of the value’s length.
Range Range A value must be between the bounds specified by the data-val-
required-min and data-val-required-max attributes. We can provide just one of these attributes to validate only a lower or upper limit for the value.
Regex RegularExpression A value must match the regular expression specified by the data-val-regexp-pattern attribute.
Equalto Compare A value must be the same as the value of the input element
specified by the data-val-equalto-other attribute.
Email - A value must be a valid email address
Uurl - A value must be a valid URL
Date - A value must be a valid date.
Number - A value must be a number (which can include decimal places)
Digits - A value must be entirely digits
Creditcard - A value must be a valid credit card number
The validation rules for which there are no equivalent C# attributes check the format of a value, but are unable to ensure that the value is really valid. For example, the creditcard validation rule checks that the value is in the correct format for a card number and that the value conforms to the Luhn encoding scheme, which is a checksum that valid cards conform to – but, of course, there is no guarantee that the value that the user has provided represents a valid card issued by a valid financial institution.
Similarly, the email and url rules ensure that the format of an email address or URL is valid, but don’t check to see if the email account or web page can be accessed – if you need to perform more rigorous validation, then a nice feature to support this is remote validation, which is described later in this chapter.
Creating Model Attributes that Support Client-Side Validation
Adding HTML attributes to our view elements is simple and direct, but it means that the validation is only applied at the client. We could cater for this by performing the same validation in the action method or model binder, but a neater technique is to create custom validation attributes which work in the same way as the built-in ones, and which trigger client- and server-side validation. Listing 26 shows a validation attribute that performs server-side validation for email addresses.
Listing 26. An attribute that performs server-side validation for email addresses public class EmailAddressAttribute : ValidationAttribute {
private static readonly Regex emailRegex = new Regex(".+@.+\\..+");
public EmailAddressAttribute() {
ErrorMessage = "Enter a valid email address";
}
public override bool IsValid(object value) { return !string.IsNullOrEmpty((string)value) &&
emailRegex.IsMatch((string)value);
} }
This is the same approach for creating server-side validation attributes that we showed you earlier in the chapter – we derive a class from ValidationAttribute and override the IsValid method to implement our validation logic.
Note We have used a very simple regular expression to validate email addresses just because we want to keep the example simple. You can easily find more comprehensive patterns to use online.
To enable client side validation, we must implement the IClientValidatable interface, which is shown in Listing 27.
Listing 27. The IClientValidatable interface public interface IClientValidatable {
IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context);
}
The interface defines one method, GetClientValidationRules, which returns an enumeration of ModelClientValidationRule objects. Each ModelClientValidationRule objects describes the client-side validation rule that we want to apply, the error message to display when the rule is broken and any parameters that the rule needs to operate. Listing 28 shows how we can add client-side support to our EmailAddressAttribute class from Listing 26.
Listing 28. Adding client-side support to the EmailAddressAttribute class public class EmailAddressAttribute : ValidationAttribute, IClientValidatable { private static readonly Regex emailRegex = new Regex(".+@.+\\..+");
public EmailAddressAttribute() {
ErrorMessage = "Enter a valid email address";
}
public override bool IsValid(object value) { return !string.IsNullOrEmpty((string)value) &&
emailRegex.IsMatch((string)value);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context) { return new List<ModelClientValidationRule> { new ModelClientValidationRule {
ValidationType = "email",
ErrorMessage = this.ErrorMessage },
new ModelClientValidationRule { ValidationType = "required", ErrorMessage = this.ErrorMessage }
};
} }
We can return as many ModelClientValidationRule objects as we need to fully capture the set of client-side rules to enforce our validation rule. In the example, we have specified that the email and required rules should be used (which we do by setting the ValidationType property of the ModelClientValidationRule class) and that both rules should use the error message from the attribute (which we do by setting the ErrorMessage property). We apply our attribute to the model class just like any other validation attribute:
public class Appointment { [EmailAddress]
public string ClientName { get; set; } ...
When the editor for the ClientName property is rendered, the view engine inspects the metadata that we have used, finds our implementation of IClientValidatable and generates the HTML attributes that we showed you in the previous section. When data values are submitted, our IsValid method is used to check the data again. Our attribute is used for client-side and server-side validation, which is neater, safer and more consistent that generating the HTML explicitly.
Creating Custom Client-Side Validation Rules
The built-in client-side validation rules shown in Table 5 are useful but not entirely comprehensive. Fortunately, we can create our own rules if we are willing to write a little JavaScript.
We are limited to adding support to the MVC client-side validation for the underlying jQuery validation rules – in essence, this means that we can tweak existing rules in useful ways, but that if we want to create anything more complex then we have to abandon the MVC client- side validation support and work directly with jQuery. Nonetheless, even though we are limited, we can still create some useful support.
For example, the client-side validation features don’t handle checkboxes properly, just like the server-side validation attributes we looked at earlier. We can create a new client-side validation rule that applies the underlying jQuery required rule to checkboxes, as shown in Listing 29.
Listing 29. Creating a custom mapping between the MVC client-side validation feature and jQuery
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.7/jquery.validate.min.js"
type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.validate.unobtrusive.js"
type="text/javascript"></script>
<script type="text/javascript">
jQuery.validator.unobtrusive.adapters.add("checkboxtrue", function (options) { if (options.element.tagName.toUpperCase() == "INPUT" &&
options.element.type.toUpperCase() == "CHECKBOX") { options.rules["required"] = true;
if (options.message) {
options.messages["required"] = options.message;
} } });
</script>
</head>
<body>
@RenderBody()
</body>
</html>
We have created a new rule called checkboxtrue which ensures that a checkbox is checked by applying the jQuery Validation required rule to the checkbox element. . We have included this script in the layout file for our project (_Layout.cshtml) so that it is available in all of our views.
Note Adding a new validation rule is an advanced topic that requires a solid understanding of the jQuery validation library and the MVC framework’s client-validation support. We are not going to explain how the script in Listing 29 works, but if you want to add new validation rules then a good place to start is to read the source code in the jQuery.validate.unobtrusive.js file.
Having created the client-side validation rule, we can create an attribute that references it. Earlier in the chapter, we showed you how to create a server-side validation attribute that ensured that a checkbox was checked. In Listing 30, we extend the attribute class to add client- side support using the checkboxtrue rule from Listing 29.
Listing 29. Adding client-side validation support to the MustBeTrueAttribute class public class MustBeTrueAttribute : ValidationAttribute, IClientValidatable { public override bool IsValid(object value) {
return value is bool && (bool)value;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context) { return new ModelClientValidationRule[] {
new ModelClientValidationRule { ValidationType = "checkboxtrue", ErrorMessage = this.ErrorMessage }};
} }
We can then apply the MustBeTrue attribute to bool properties in our model classes to ensure that the user checks the box before submitting the data to the server.
Performing Remote Validation
The last validation feature we will look at in this chapter is remote validation. This is a client- side validation technique that invokes an action method on the server to perform validation.
A common example of remote validation is to check whether a user name is available in applications when such names must be unique – the user submits the data and the client-side validation is performed. As part of this process, an AJAX Ajax request is made to the server to validate the user name that has been requested – if the user name has been taken, a validation error is displayed so that the user can enter another value.
This may seem like regular server-side validation, but there are some benefits to this approach. Firstly, only some properties will be remotely validated – the client-side validation benefits still apply to all of the other data values that the user has entered. Secondly, the request is relatively lightweight and is focused on validation, rather than processing an entire model object – this means that we can minimize the performance impact that the requests generate.
The third difference is that the remote validation is performed in the background – the user doesn’t have to press the submit button and then wait for a new view to be rendered and returned. It makes for a more responsive user experience, especially when there is a modestly fastslow network between the browser and the server.
That said, remote validation is a compromise – it allows us to strike a balance between client-side and server-side validation, but it does require requests to the application server and it is not as quick to validate as normal client-side validation.
Tip We explain the MVC framework support for AJAX Ajax and JSON in Chapter 19.
The first step towards using remote validation is to create an action method that can validate one of our model properties. We are going to validate the Date property of our
Appointment model, to ensure that the requested appointment is in the future (this is one of the original validation rules we used at the start of the chapter). Listing 30 shows the ValidateDate method we have added to our AppointmentController class.
Listing 30. Adding a validation method to the controller public class AppointmentController : Controller { private IAppointmentRepository repository;
public AppointmentController(IAppointmentRepository repo) { repository = repo;
}
public ViewResult MakeBooking() {
return View(new Appointment { Date = DateTime.Now });
}
public JsonResult ValidateDate(string Date) { DateTime parsedDate;
if (!DateTime.TryParse(Date, out parsedDate)) { return Json("Please enter a valid date (mm/dd/yyyy)", JsonRequestBehavior.AllowGet);
} else if (DateTime.Now > parsedDate) {
return Json("Please enter a date in the future", JsonRequestBehavior.AllowGet);
} else {
return Json(true, JsonRequestBehavior.AllowGet);
} }
[HttpPost]
public ViewResult MakeBooking(Appointment appt) { if (ModelState.IsValid) {
repository.SaveAppointment(appt);
return View("Completed", appt);
} else { return View();
} }
}
Actions methods that support remote validation must return the JsonResult type and the method parameter must match the name of the field being validated – in our case this is Date. We make sure that we can parse a DateTime object from the value that the user has submitted and, if we can, check to see that the date is in the future.
Tip We could have taken advantage of model binding so that the parameter to our action method would be a DateTime object, but doing so would mean that our validation method wouldn’t be called if the user entered a nonsense value like apple, for example. This is because the model binder wouldn’t have been able to create a DateTime object from apple and throws an exception when it tries. The remote validation feature doesn’t have a way to express the exception and so it is quietly discarded – this has the unfortunate effect of not highlighting the data field and so creating the impression that the value that the user has entered is valid. As a generate rule, the best approach to remote validation is to accept a string parameter in the action method and perform any type conversion, parsing or model binding explicitly.
We express validation results using the Json method, which creates a JSON formatted result that the client-side remote validation script can parse and process. If the value that we are processing meets our requirements, then we pass true as the parameter to the Json method, like this:
return Json(true, JsonRequestBehavior.AllowGet);
If we are unhappy with the value, we pass the validation error message that the user should see as the parameter, like this:
return Json("Please enter a date in the future", JsonRequestBehavior.AllowGet);
In both cases, we must also pass the JsonRequestBehavior.AllowGet value as a parameter as well – this is because the MVC framework disallows GET requests that produce JSON by default and we have to override this behavior – without this additional parameter, the validation request will quietly fail and no validation errors will be displayed to the client.
Caution The validation action method will be called when the user first submits the form and then again each time they edit the data – in essence, every keystroke will lead to a call to our server. For some applications, this can be a significant number of requests, and must be taken into account when specifying