MVC Data Model Attributes

1. When creating a data model to be used by Views which bind to it, there are a number of Attributes that can be set on the members of the data object which control how MVC deals with them. Here are a list of the common ones.

2. [Required]
[Required(ErrorMessage = "Can't leave name blank!")]
 
When this is added to a data member, then the field must contain a value when submit is pressed. If the field has an @Html.ValidationMessageFor element for the data item, then the unobtrusive javascript will display a 'blah must be completed' message or the provided ErrorMessage if it is left blank, and the Page will be invalidated.

[StringLength(6)]
[StringLength(6, MinimumLength = 3)]
[StringLength(6, MinimumLength = 3, ErrorMessage="3 to 6 chars only")]
 
When this is added to a data member, then the field can be validated for string length. If the field has an @Html.ValidationMessageFor element for the data item, then the unobtrusive javascript will display a 'blah must be between 3 and 6 characters' message or 'must be less than 6 characters' message or the given ErrorMessage if falls outside the criterion, and the Page will be invalidated.

[Display(Name = "User Name")]
 
When this is added to a data member, then the LabelFor element associated with a data item will be set to this Name, allowing you to set the display label from the object side. No validation involved.

[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")]
 
When this is added to a data member, then the input is validated according to the Regular Expression given. If the field has an @Html.ValidationMessageFor element for the data item, then the unobtrusive javascript will display the ErrorMessage when it fails this validation, and the Page will be invalidated.

[ScaffoldColumn(false)]
 
When this is added to a data member before the View is generated, then MVC will omit it from Scaffolding, and no generated EditorFor will be put into the View. This is useful for data members which are not editable and do not need to be persisted.

[DataType(DataType.EmailAddress)]
[DataType(DataType.Password)]
[DataType(DataType.PhoneNumber), ErrorMessage = "Not a valid phone number")]
 
When this is added to a data member, then the input is validated according to the rules of the DataType enum and a suitable error message or the given ErrorMessage is generated. The DataType enum contains several types which enable masking and validation, and password will mask the text etc.

[Compare("Password")]
[Compare("Password", ErrorMessage = "Password do not match!")]
 
When this is added to a data member then this data item is compared with the named data item and validated if they do not match. This is useful when you need to ask the user to enter a password twice, for instance. This is part of the Sytem.Web.Mvc namespace rather than the usual System.ComponentModel.DataAnnotations namespace.

3. An example of a data class using these attributes:

    public class UserModel
    {
        [Required]
        [StringLength(6, MinimumLength = 3)]
        [Display(Name = "User Name")]
        [RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")]
        [ScaffoldColumn(false)]
        public string UserName { get; set; }

        [Required]
        [StringLength(8, MinimumLength = 3)]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [Required]
        [StringLength(9, MinimumLength = 2)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        
        [Required]
        public string City { get; set; }

    }
4. You can create your own custom Attributes and validation. Imagine you wanted to create a [NotHitler] attribute to attach to a text field that would fire a validation if the name entered wasn't Hitler. Stupid example, but anyway.... we create the Attribute in code somewhere....

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

public sealed class NotHitlerAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if ((value as string).ToLower() != "hitler")
        {
            return new ValidationResult(string.Format("'{0}' is not Hitler!", validationContext.DisplayName));
        }
        return null;
    }
}   
Now we can annotate the model with our new [NotHitler] attribute:
    public class UserModel
    {
        [Required]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [NotHitler]
        [Required]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
    }
And this validates (server-side only for now) when you type a name that isn't Hitler:

Name
First Name
Last Name 'Last Name' is not Hitler!
5. To get the client-side bit working, we need to do two main things. Make the attribute also inherit from IClientValidatable and implement that client rule, and then add appropriate handlers in the javascript. Adjust the Attribute to the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

public sealed class NotHitlerAttribute : ValidationAttribute, IClientValidatable // note the extra inheritance!
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if ((value as string).ToLower() != "hitler")
        {
            return new ValidationResult(string.Format("'{0}' is not Hitler!", validationContext.DisplayName));
        }
        return null;
    }

    // extra method to implement to create rule list (only one rule this time)
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule =  new ModelClientValidationRule();
        rule.ErrorMessage = string.Format("'{0}' is not Hitler!", metadata.GetDisplayName());
        rule.ValidationType = "nothitler";
        rule.ValidationParameters.Add("fieldtocheck", metadata.PropertyName);
        yield return rule;
    }
}   
This takes a bit of explaining. In the C# code, we create a 'rule' and add the error message as before, the name the JS function will be referenced by, and a validation parameter, called "fieldtocheck". This parameter is set to the PropertyName of the dtatelement calling it. We know that MVC when using Scaffolding, uses the Property name to name the HTML element it refers to. So by passing it in as a parameter, we're really telling the JS the id of the HTML element to go get the value from when it needs to compare it. Not if you need more than one parameter (e.g. the validator needs to compare two fields instead of one) this is where you would set extra parameters, as many as you need.

And now we need to add the handler and adaptor to the javascript objects, so create a JavaScript include file for the Edit View or else emebed on the View page:

<script type="text/javascript" language=javascript" > 

    jQuery.validator.unobtrusive.adapters.add("nothitler", ["fieldtocheck"], function (options) {
        options.rules["nothitler"] = "#" + options.params.fieldtocheck;
        options.messages["nothitler"] = options.message;
    });  

    jQuery.validator.addMethod("nothitler", function (value, element, param) {
        return !($(param).val().toLowerCase() != "hitler");
    });

</script> 
Right, the top function is the one which registers the handler - it also extracts the 'fieldtocheck' paremeter passed to it, and appends a # on the front. This is because jQuery uses the '#htmlid' format to locate elements in the document, so may as well do it here. Also the error message is extracted. These two things are stored in the options[] object for this named rule.

Incidentally the ["fieldtocheck"] parameter is your comma separated list (JSON) of params you added in C#. if you added a second one in code...
    rule.ValidationParameters.Add("fieldtocheck", metadata.PropertyName);
    rule.ValidationParameters.Add("anotherone", "whoknows");
... then this would be made available to you by using ["fieldtocheck","anotherone"] in the second argument, and this would be deJSONed into a options.params.anotherone variable for use in your function. You would have to combine it with the other one (since you can only pass in one param object to the actual rule handler) so it might make sense not to extract the bits out of options.params here at all, but just to pass the entire params object to the rule and let the handler get the bits, e.g in the function registering the handler:
    options.rules["nothitler"] = options.params;
and then use the bits directly in the handler itself (haven't checked this works, though, you try!):
    var fieldtocheck = param.fieldtocheck;
    var anotherone = param.anotherone;
The bottom function is the actual handler. The param contains the '#htmlid' name of the element to check, so we simply use jQuery to fetch it and then compare it with "hitler" to return true or false. This function is supposed to return true if there is no error, so to make it fire when the name is NOT hitler, we need to negate the condition of it being unequal.. if you get what I mean.. oh just experiment.

The unobtrusive code written by MS does the rest. Voila! Client Side Validation of your custom Attribute validation.

If it doesn't work, make sure you have turned on Client Side Validation in the View (or alternatively in web.config):
    HtmlHelper.ClientValidationEnabled = true;
    HtmlHelper.UnobtrusiveJavaScriptEnabled = true; 
And that's a basic introduction to MVC data model attributes, as well as writing custom attributes with client and server validation!