JQuery Data Annotations – Ignore MVC validators in Meta Data.

I mentioned in a previous blog post that I was looking at jQuery Data Annotations.   This is really smart and provides more client-side attribute support out of the box than comes with MVC2.  It’s extendable so you can create your own attributes with corresponding validation.

I found a slight glitch using JDA though.   I had encountered a problem where the meta data was applying the MVC required attribute for non-null types.  There’s a solution for this, see my other post.

I set this flag and sure enough the required attribute was dropped.  I did have another problem though, the MVC Numeric validator was automatically being applied to numeric types.   The ignore flag didn’t seem to have changed this behaviour.

In the end I took the JDA source code and made a small change.  Now it basically ignores any of the MVC validators returned from the meta data if the ignore flag has been set.  Original source code is available from the JDA site.  Changes are shown below in brown, wrapped so you can see them.

Modified source code for jQueryValidationExtension.cs :

namespace System.Web.Mvc.Html
{
 using System;
 using System.ComponentModel; using System.Collections.Generic;
 using System.Data.Linq;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Linq.Expressions;
 using System.Text;
 using System.Web.Mvc.Resources;
 using System.Web.Routing;
 using jQuery.Validation.DataAnnotations;

   public static class jQueryValidationExtensions
   {

     [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
     public static MvcHtmlString jQueryValidationFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
     {
         StringBuilder script = new StringBuilder();
         List<string> messages = new List<string>();
         List<string> validators = new List<string>();

         foreach (ModelValidator validator in ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).GetValidators(htmlHelper.ViewContext.Controller.ControllerContext))
         {
             if (validator.ShouldBeIgnored())
                 break;
             IEnumerable<ModelClientValidationRule> rules = validator.GetClientValidationRules();
             foreach (var a in rules)
             {
                 validators.Add(a.ValidationParameters["jQueryValidationInit"].ToString());
                 messages.Add(string.Format("{0}: \"{1}\"", a.ValidationParameters["jQueryName"].ToString(), a.ErrorMessage));                        
             }
         }

         foreach(Attribute valAttr in ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).ContainerType.GetCustomAttributes(true))
         {
             if(valAttr.GetType().Equals(typeof(EqualToAttribute)))
             {
                 EqualToAttribute attr = (EqualToAttribute)valAttr;
                 ModelMetadata.FromLambdaExpression(model => model, htmlHelper.ViewData);

                 EqualToValidator validator = new EqualToValidator(ModelMetadata.FromLambdaExpression(model => model, htmlHelper.ViewData), htmlHelper.ViewContext.Controller.ControllerContext, attr);
                 foreach (var a in validator.GetClientValidationRules())
                 {
                     if (ExpressionHelper.GetExpressionText(expression) == attr.SourceProperty)
                     {
                         validators.Add(a.ValidationParameters["jQueryValidationInit"].ToString());
                          messages.Add(string.Format("{0}: \"{1}\"", a.ValidationParameters["jQueryName"].ToString(), a.ErrorMessage));                            
                     }
                 }
             }
         }

         script.AppendFormat("$(\"#{0}\").rules(\"add\", {{", ExpressionHelper.GetExpressionText(expression));
         for (int i = 0; i < validators.Count; i++)
         {
             script.AppendFormat("{0} ,", validators[i]);
         }

         script.Append("messages: {");
         for (int i = 0; i < messages.Count; i++)
         {
             script.Append(messages[i]);
             if(i != messages.Count - 1)
             {
                 script.Append(",");
             }
         }

         script.Append("}});");
         return MvcHtmlString.Create(script.ToString());
     }

     private static bool MvcValidationIsSwitchedOff()
     {
         return (!DataAnnotationsModelValidatorProvider.
                    AddImplicitRequiredAttributeForValueTypes);
     }

     public static bool ShouldBeIgnored(this ModelValidator validator)
     {
         return (MvcValidationIsSwitchedOff() &&
                 validator.IsAnMvcValidator());            
     }

     public static bool IsAnMvcValidator(this ModelValidator validator)
     {
         return validator.GetType().BaseType.FullName.
                  Equals(typeof (ModelValidator).FullName);
     }
  }
}
Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s