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!")] [StringLength(6)] [StringLength(6, MinimumLength = 3)] [StringLength(6, MinimumLength = 3, ErrorMessage="3 to 6 chars only")] [Display(Name = "User Name")] [RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed")] [ScaffoldColumn(false)] [DataType(DataType.EmailAddress)] [DataType(DataType.Password)] [DataType(DataType.PhoneNumber), ErrorMessage = "Not a valid phone number")] [Compare("Password")] [Compare("Password", ErrorMessage = "Password do not match!")] |
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: |
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! |