One of the many nice things they have added in MVC 2 is model/view model validation on both the client and server side. I won’t go into the basics here since they have been covered extensively by others. However, recently I was adding validation to require a value for a hidden field on a form, and found that the default validation functionality just didn’t work on the client side for a hidden field. Here I will go over how I added some custom validation to allow me to do it.
While it might sound weird to validate a hidden field, something a user will never directly interact with, in this age of complex interfaces built in JavaScript it’s easy to imagine a scenario where a series of interactions leads to storing some kind of value in a hidden field. If that end value is something that is required, it’s only natural that that should be enforced by the model.
First, let’s start by defining a very simple view model for the form:
public class DemoForm
{
[Required(ErrorMessage = "You must enter a name")]
public string Name { get; set; }
}
It just defines a single property for storing a name. Now we can define a very basic view that is strictly typed to that view model. Here is the code for setting up the form:
<% using (Html.BeginForm())
{ %>
<%= Html.HiddenFor(model => model.Name) %>
<p>
<%= Html.ValidationMessageFor(model => model.Name) %>
</p>
<input type="submit" value="Submit" />
<% } %>
If you run the page now, you will get correct server-side validation, but nothing on the client. To get client validation in MVC, we need to tell the page to enable client validation. To do that, call this before starting the form:
<% Html.EnableClientValidation(); %>
You will also need some JavaScript references to these files, which are included by default in any new MVC project:
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/MicrosoftAjax.js") %>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/MicrosoftMvcAjax.js") %>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/MicrosoftMvcValidation.js") %>"></script>
Now if you reload the page, client validation still isn’t working. Why not? After digging through the Microsoft validation scripts a bit, I found that the Required validator ignores hidden fields altogether in JavaScript. We could change it from being a hidden field to a text field and the same code would work as expected, but that’s not what we want to do.
As it turns out, they made it rather simple to extend the built-in validation with your own custom rules. First, we’ll define the attribute class, so that it can be added as a decorator to a property the same way the other validators work. In this case, since the Required attribute actually works fine on the server side, our class will extend that and do nothing else. This way we can provide the client side functionality and rely on the existing server validation.
public class HiddenRequiredAttribute : RequiredAttribute
{
}
Now that we have the attribute, we need a validator that uses it. To do that, we extend the DataAnnotationsModelValidator class, and tell it to use the attribute we just defined:
public class HiddenRequiredValidator : DataAnnotationsModelValidator<HiddenRequiredAttribute>
{
private string _errorMessage;
public HiddenRequiredValidator(ModelMetadata metaData, ControllerContext context, HiddenRequiredAttribute attribute)
: base(metaData, context, attribute)
{
_errorMessage = attribute.ErrorMessage;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = _errorMessage,
ValidationType = "hiddenRequired"
};
return new[] { rule };
}
}
The important part to notice here is where we set the ValidationType to “hiddenRequired.” This is the unique identifier we define for referencing our validator in JavaScript. Remember it, because you’re going to need it later.
So now we have an attribute, and a validator for it. Next up, we need to register our validator in the application’s Application_Start function:
DataAnnotationsModelValidatorProvider
.RegisterAdapter(typeof(HiddenRequiredAttribute), typeof(HiddenRequiredValidator));
Now let’s switch the view model’s Name property to use the new HiddenRequired attribute instead of the Required one we had before:
public class DemoForm
{
[HiddenRequired(ErrorMessage = "You must enter a name")]
public string Name { get; set; }
}
All that remains is to put in some JavaScript code to handle the validation. I’ll show the code here, and then discuss it. Since this is a contained demo, I will add the code directly to the view.
Sys.Mvc.ValidatorRegistry.validators["hiddenRequired"] = function(rule)
{
return function(value, context)
{
if (value)
{
return true;
}
return rule.ErrorMessage;
}
};
If that code looks simple, that’s because it is. Since we’re only validating that the field has some kind of value, that is all we need to check for. The reason that the return function is wrapped in another function is to allow for adding more complex logic before the return function if needed. For this example, it isn’t needed.
Now if you reload the page you will get the client-side validation we were aiming for, triggered by the submit button. Always remember that client-side validation is just there for improving the user experience, and is no substitute for server validation.