ASP.NET MVC Validation Complete

Posted by Ricardo Peres on ASP.net Weblogs See other posts from ASP.net Weblogs or by Ricardo Peres
Published on Sun, 03 Jun 2012 12:10:52 GMT Indexed on 2012/06/03 16:41 UTC
Read the original article Hit count: 718

OK, so let’s talk about validation. Most people are probably familiar with the out of the box validation attributes that MVC knows about, from the System.ComponentModel.DataAnnotations namespace, such as EnumDataTypeAttribute, RequiredAttribute, StringLengthAttribute, RangeAttribute, RegularExpressionAttribute and CompareAttribute from the System.Web.Mvc namespace. All of these validators inherit from ValidationAttribute and perform server as well as client-side validation. In order to use them, you must include the JavaScript files MicrosoftMvcValidation.js, jquery.validate.js or jquery.validate.unobtrusive.js, depending on whether you want to use Microsoft’s own library or jQuery. No significant difference exists, but jQuery is more extensible.

You can also create your own attribute by inheriting from ValidationAttribute, but, if you want to have client-side behavior, you must also implement IClientValidatable (all of the out of the box validation attributes implement it) and supply your own JavaScript validation function that mimics its server-side counterpart. Of course, you must reference the JavaScript file where the declaration function is. Let’s see an example, validating even numbers. First, the validation attribute:

   1: [Serializable]
   2: [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
   3: public class IsEvenAttribute : ValidationAttribute, IClientValidatable
   4: {
   5:     protected override ValidationResult IsValid(Object value, ValidationContext validationContext)
   6:     {
   7:         Int32 v = Convert.ToInt32(value);
   8:  
   9:         if (v % 2 == 0)
  10:         {
  11:             return (ValidationResult.Success);
  12:         }
  13:         else
  14:         {
  15:             return (new ValidationResult("Value is not even"));
  16:         }
  17:     }
  18:  
  19:     #region IClientValidatable Members
  20:  
  21:     public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
  22:     {
  23:         yield return (new ModelClientValidationRule() { ValidationType = "iseven", ErrorMessage = "Value is not even" });
  24:     }
  25:  
  26:     #endregion
  27: }

The iseven validation function is declared like this in JavaScript, using jQuery validation:

   1: jQuery.validator.addMethod('iseven', function (value, element, params)
   2: {
   3:     return (true);
   4:     return ((parseInt(value) % 2) == 0);
   5: });
   6:  
   7: jQuery.validator.unobtrusive.adapters.add('iseven', [], function (options)
   8: {
   9:     options.rules['iseven'] = options.params;
  10:     options.messages['iseven'] = options.message;
  11: });

Do keep in mind that this is a simple example, for example, we are not using parameters, which may be required for some more advanced scenarios.

As a side note, if you implement a custom validator that also requires a JavaScript function, you’ll probably want them together. One way to achieve this is by including the JavaScript file as an embedded resource on the same assembly where the custom attribute is declared. You do this by having its Build Action set as Embedded Resource inside Visual Studio:

image

Then you have to declare an attribute at assembly level, perhaps in the AssemblyInfo.cs file:

   1: [assembly: WebResource("SomeNamespace.IsEven.js", "text/javascript")]

In your views, if you want to include a JavaScript file from an embedded resource you can use this code:

   1: public static class UrlExtensions
   2: {
   3:     private static readonly MethodInfo getResourceUrlMethod = typeof(AssemblyResourceLoader).GetMethod("GetWebResourceUrlInternal", BindingFlags.NonPublic | BindingFlags.Static);
   4:  
   5:     public static IHtmlString Resource<TType>(this UrlHelper url, String resourceName)
   6:     {
   7:         return (Resource(url, typeof(TType).Assembly.FullName, resourceName));
   8:     }
   9:  
  10:     public static IHtmlString Resource(this UrlHelper url, String assemblyName, String resourceName)
  11:     {
  12:         String resourceUrl = getResourceUrlMethod.Invoke(null, new Object[] { Assembly.Load(assemblyName), resourceName, false, false, null }).ToString();
  13:         return (new HtmlString(resourceUrl));
  14:     }
  15: }

And on the view:

   1: <script src="<%: this.Url.Resource("SomeAssembly", "SomeNamespace.IsEven.js") %>" type="text/javascript"></script>

Then there’s the CustomValidationAttribute. It allows externalizing your validation logic to another class, so you have to tell which type and method to use. The method can be static as well as instance, if it is instance, the class cannot be abstract and must have a public parameterless constructor. It can be applied to a property as well as a class. It does not, however, support client-side validation. Let’s see an example declaration:

   1: [CustomValidation(typeof(ProductValidator), "OnValidateName")]
   2: public String Name
   3: {
   4:     get;
   5:     set;
   6: }

The validation method needs this signature:

   1: public static ValidationResult OnValidateName(String name)
   2: {
   3:     if ((String.IsNullOrWhiteSpace(name) == false) && (name.Length <= 50))
   4:     {
   5:         return (ValidationResult.Success);
   6:     }
   7:     else
   8:     {
   9:         return (new ValidationResult(String.Format("The name has an invalid value: {0}", name), new String[] { "Name" }));
  10:     }
  11: }

Note that it can be either static or instance and it must return a ValidationResult-derived class. ValidationResult.Success is null, so any non-null value is considered a validation error. The single method argument must match the property type to which the attribute is attached to or the class, in case it is applied to a class:

   1: [CustomValidation(typeof(ProductValidator), "OnValidateProduct")]
   2: public class Product
   3: {
   4: }

The signature must thus be:

   1: public static ValidationResult OnValidateProduct(Product product)
   2: {
   3: }

Continuing with attribute-based validation, another possibility is RemoteAttribute. This allows specifying a controller and an action method just for performing the validation of a property or set of properties. This works in a client-side AJAX way and it can be very useful. Let’s see an example, starting with the attribute declaration and proceeding to the action method implementation:

   1: [Remote("Validate", "Validation")]
   2: public String Username
   3: {
   4:     get;
   5:     set;
   6: }

The controller action method must contain an argument that can be bound to the property:

   1: public ActionResult Validate(String username)
   2: {
   3:     return (this.Json(true, JsonRequestBehavior.AllowGet));
   4: }

If in your result JSON object you include a string instead of the true value, it will consider it as an error, and the validation will fail. This string will be displayed as the error message, if you have included it in your view.

You can also use the remote validation approach for validating your entire entity, by including all of its properties as included fields in the attribute and having an action method that receives an entity instead of a single property:

   1: [Remote("Validate", "Validation", AdditionalFields = "Price")]
   2: public String Name
   3: {
   4:     get;
   5:     set;
   6: }
   7:  
   8: public Decimal Price
   9: {
  10:     get;
  11:     set;
  12: }

The action method will then be:

   1: public ActionResult Validate(Product product)
   2: {
   3:     return (this.Json("Product is not valid", JsonRequestBehavior.AllowGet));
   4: }

Only the property to which the attribute is applied and the additional properties referenced by the AdditionalFields will be populated in the entity instance received by the validation method. The same rule previously stated applies, if you return anything other than true, it will be used as the validation error message for the entity. The remote validation is triggered automatically, but you can also call it explicitly. In the next example, I am causing the full entity validation, see the call to serialize():

   1: function validate()
   2: {
   3:     var form = $('form');
   4:     var data = form.serialize();
   5:     var url = '<%: this.Url.Action("Validation", "Validate") %>';
   6:  
   7:     var result = $.ajax
   8:     (
   9:         {
  10:             type: 'POST',
  11:             url: url,
  12:             data: data,
  13:             async: false
  14:         }
  15:     ).responseText;
  16:  
  17:     if (result)
  18:     {
  19:         //error
  20:     }
  21: }

Finally, by implementing IValidatableObject, you can implement your validation logic on the object itself, that is, you make it self-validatable. This will only work server-side, that is, the ModelState.IsValid property will be set to false on the controller’s action method if the validation in unsuccessful. Let’s see how to implement it:

   1: public class Product : IValidatableObject
   2: {
   3:     public String Name
   4:     {
   5:         get;
   6:         set;
   7:     }
   8:  
   9:     public Decimal Price
  10:     {
  11:         get;
  12:         set;
  13:     }
  14:  
  15:     #region IValidatableObject Members
  16:     
  17:     public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
  18:     {
  19:         if ((String.IsNullOrWhiteSpace(this.Name) == true) || (this.Name.Length > 50))
  20:         {
  21:             yield return (new ValidationResult(String.Format("The name has an invalid value: {0}", this.Name), new String[] { "Name" }));
  22:         }
  23:         
  24:         if ((this.Price <= 0) || (this.Price > 100))
  25:         {
  26:             yield return (new ValidationResult(String.Format("The price has an invalid value: {0}", this.Price), new String[] { "Price" }));
  27:         }
  28:     }
  29:     
  30:     #endregion
  31: }

The errors returned will be matched against the model properties through the MemberNames property of the ValidationResult class and will be displayed in their proper labels, if present on the view.

On the controller action method you can check for model validity by looking at ModelState.IsValid and you can get actual error messages and related properties by examining all of the entries in the ModelState dictionary:

   1: Dictionary<String, String> errors = new Dictionary<String, String>();
   2:  
   3: foreach (KeyValuePair<String, ModelState> keyValue in this.ModelState)
   4: {
   5:     String key = keyValue.Key;
   6:     ModelState modelState = keyValue.Value;
   7:  
   8:     foreach (ModelError error in modelState.Errors)
   9:     {
  10:         errors[key] = error.ErrorMessage;
  11:     }
  12: }

And these are the ways to perform date validation in ASP.NET MVC. Don’t forget to use them!

© ASP.net Weblogs or respective owner

Related posts about .NET

Related posts about AJAX