Extending EntLib VAB - Part 2.2: Enable unobtrusive jQuery validation for Custom Validator


Note: Thank you for visiting my blog. However, this blog is not maintained actively anymore. This post is now also in my new blog. Feel free to leave a comment there.

… Continuation from Extending EntLib VAB - Part 2.1: Create custom validators and integrate that with EntLib CABC.

Now lets see how we can enable client side unobtrusive validation for this custom validator by creating adapter for the same and adding the adapter in our custom implementation of DataAnnotationsModelValidatorProvider  (explained in Extending EntLib VAB - Part 1: Enable client side support EntLib VAB and align the validation error keys with MVC ModelState keys) and finally writing unobtrusive jQuery validation.

Step 1: Create custom Adapter for our custom validator.
public class NumberOnlyAttributeAdapter : DataAnnotationsModelValidator<NumberOnlyAttribute>

{

    private readonly string _key;

    public NumberOnlyAttributeAdapter(ModelMetadata metadata, ControllerContext context, NumberOnlyAttribute attribute)

        : base(metadata, context, attribute)

    {

        _key = metadata.PropertyName;

    }

 

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()

    {

        ModelClientValidationRule rule = new ModelClientValidationRule

            {

                ErrorMessage = String.Format(CultureInfo.CurrentCulture, this.Attribute.MessageTemplate, _key),

                //make sure you add value of validation type and validation parameter name in lower case, otherwise it will result in runtime error.

                ValidationType = "numberonly" 

            };

        yield return rule;

    }

}

Step 2: Integrate this adapter with our custom implementation of DataAnnotationsModelValidatorProvider. The code snippet is shown below:
public class EntLibDataAnotationProvider : DataAnnotationsModelValidatorProvider

{

    #region Fields

    private static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory> 

    {         

        /*Rest of the code has been removed for brevity*/

        { 

            typeof(NumberOnlyAttribute), 

            (metadata, context, attribute) => new NumberOnlyAttributeAdapter(metadata, context, (NumberOnlyAttribute)attribute) 

        } 

    };

    #endregion

 

    /* 

    Implementation details of the follwoing methods has been omitted for Brevity 

    (and also for the reason that there is no change in these functions):

    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)

    private IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<ValidatorData> validatorData)

    */

 

    #region Private Methods

    private ModelValidator GetValidator(ModelMetadata metadata, ControllerContext context, ValidatorData item)

    {

        DataAnnotationsModelValidationFactory factory;

        /*Rest of the code has been removed for brevity*/

        else if (item.GetType().Name == "NumberOnlyValidatorData")

        {

            /*  We have not really used the NumberOnlyValidatorData here, 

                but the same can be used to retrive any property related to NumberOnlyValidator as set in the config xml

                and accordingly the properties can be used to instantiate the custom validation attribute.

            */

            NumberOnlyValidatorData numberOnlyData = (NumberOnlyValidatorData)item;

 

            NumberOnlyAttribute numOnlyAttribute = new NumberOnlyAttribute { MessageTemplate = item.GetMessageTemplate() };

 

            if (AttributeFactories.TryGetValue(numOnlyAttribute.GetType(), out factory))

                return factory(metadata, context, numOnlyAttribute);

        }

 

        return null;

    }

    #endregion

}
Once you have done this far, the “data-val-X” attributes will be rendered for the properties (of your UxModel) on which you have applied this custom validator. For example:
<input data-val="true" data-val-numberonly="Only Integer Input Allowed" 
data-val-required="Another Name is required." id="AnotherProp_AnotherName" 
name="AnotherProp.AnotherName" type="text" value="" />

We are almost done. As these data-val-X attributes are rendered for the properties only thing left is to write jQuery unobtrusive validation and refer the same in your view.

Step 3: Writing unobtrusive jQuery validation:
$.validator.addMethod('numberonly', function (value, element, params) {

    //Yes, I agree there is a mismatch in client side and server side validation here, but this is just to give you the idea.

    var regex = new RegExp('^\\d*$'); 

    return regex.test(value);

});

 

$.validator.unobtrusive.adapters.add('numberonly', [], function (options) {

    options.rules['numberonly'] = true;

    options.messages['numberonly'] = options.message;

});

Step 4: Refer this script in view or add it to appropriate bundle:
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(

            "~/Scripts/jquery.unobtrusive*",

            "~/Scripts/jquery.validate*",

            "~/Content/CustomScripts/UnobtrusiveValidators/NumberOnlyValidator.js"));

And that’s it… You are ready to Rock N’ Roll.
Hope you have enjoyed this series.

Happy Coding.
Sayan

P.S: This is written based on my personal experience, if there is a better way to do it, please don't hesitate to leave your comments.

3 comments: (+add yours?)

Anonymous said...

Thank you a lot on the good explanation on VAB and the linkage to ASP MVC.

I have two questions:
1. how to register the VAB by code and not by the EntLib Console and not with its xml config file?
2. How can we, make the Validator inject each view, with the jscript for the client side validation?

Regards.

Unknown said...

Thank You for reading my blog.

Though I am not sure about the specific answer to your questions, here I can point you to some resources that I find to be very helpful.
1. Developer's Guide for Enterprise Library
2. Hands-on labs

You can of course define the validators in code using the validator attributes:
public class Address
{
[StringLengthValidator(1, 50)]
public string StreetAddress { get; set; }
....
}
But i find that to be inflexible, but please go ahead if that suits your purpose.

To answer your next question, I think doing that would a bit complicated. But again the question is why you want to do so? There are few questions need to be addressed for this, such as: On what basis you would like to 'inject' the client side validation? If you think of this custom validator as one individual component, how will you know what kind of UI framework (ASP.NET MVC, Angular App, or KnockoutJS App etc.) is going to be used, which may determine the nature of client side validation. Also if we just consider the ASP.NET MVC, the client side validation is not part of ASP.NET and rather part of jQuery unobtrusive validation framework. And it is purely my opinion that following that pattern also helps to keep the code clean and achieve a clear separation of concern.

Hope this helps.

Thank You.

Anonymous said...

Yeah, it does help.
Thank you again for your good time.

My best wishes.

Post a Comment