Showing posts with label Validation. Show all posts
Showing posts with label Validation. Show all posts

Wednesday, December 29, 2010

ASP.NET MVC 3 Custom Validation

Data annotations Rock!  Using data annotations on your model classes make data validation (client and server) trivial.  Just add the data annotation to you model class, wire up an edit/create view to your controller via the 'Add View' wizard, and you have client- and server-side validation with very little effort.

A little disclaimer here - the implementation below it not MVC 3 specific; however, I developed the solution using MVC 3, tested using MVC 3, and it works using MVC 3.  I'm pretty sure it will work as-is using MVC 2.

What if you need validation that is not provided via data annotations out-of-box?  You create your own.  That's what this post is all about.

Creating your own validators that support client- and server-side validation is essentially a four-step process:
  1. Create a custom attribute that extends the ValidationAttribute, or better yet, you extend one of the existing data annotations.
  2. Create a custom validator that extends the DataAnnotationsModelValidator, where T is the type of custom attribute you created in step one.
  3. Create a client-side script to handle the client-side validation.
  4. Register the attribute/validator classes in your app's bootstrapper or Global.asax.
Side note: It seems that every time I have a good idea for a post, Phil Haack has beat me to the punch... and this pattern is also true in this scenario :-) For a similar article on the same subject, please see Phil's ASP.NET MVC 2 Custom Validation post.

This post is very code centric; therefore, if the concepts are unfamiliar or a brushing up is necessary, please take a look at Phil's post.  Also, Brad Wilson has a few different blog posts/series on Data Annotations and ModelMetadata.  Brad and Phil's knowledge of the ins and outs of anything ASP.NET MVC (and arguably C#) scares me...

The custom attribute and validator that I'm going to create is an Email validator.  There are plenty examples on the net, including here, here, etc...  What these email validator solutions don't provide is client-side validation.  I will show you how.  So, lets start with step one:

1. Create a Custom Attribute
Below are two solutions.  They both result in the same functionality.  The first solution is a full implementation that extends the ValidationAttribute class, while the second solution extends the RegularExpressionAttribute.

EmailAttribute Extending Validation Attribute
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Validation
{
    /// <summary>
    /// Email Attribute class used for email validation. Used similar to the System.ComponentModel.DataAnnotations.RegularExpressionAttribute.
    /// </summary>
    public class EmailAttribute : ValidationAttribute
    {
        #region Properties

        /// <summary>
        /// Gets or sets the Regular expression.
        /// </summary>
        /// <value>The regex.</value>
        private Regex Regex { get; set; }

        /// <summary>
        /// Gets the pattern used for email validation.
        /// </summary>
        /// <value>The pattern used for email validation.</value>
        /// <remarks>
        /// Regular Expression Source - Comparing E-mail Address Validating Regular Expressions
        /// <see cref="http://fightingforalostcause.net/misc/2006/compare-email-regex.php"/>
        /// </remarks>
        public string Pattern
        {
            get
            {
                return
                    @"^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-zA-Z0-9]{1}[a-zA-Z0-9\-]{0,62}[a-zA-Z0-9]{1})|[a-zA-Z])\.)+[a-zA-Z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$";
            }
        }

        #endregion Properties

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the <see cref="EmailAttribute"/> class.
        /// </summary>
        public EmailAttribute()
        {
            this.Regex = new Regex(this.Pattern);
        }

        #endregion Ctors

        /// <summary>
        /// Determines whether the specified value of the object is valid.
        /// </summary>
        /// <param name="value">The value of the object to validate.</param>
        /// <returns>
        /// true if the specified value is valid; otherwise, false.
        /// </returns>
        public override bool IsValid(object value)
        {
            // convert the value to a string
            var stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);

            // automatically pass if value is null or empty. RequiredAttribute should be used to assert an empty value.
            if (string.IsNullOrWhiteSpace(stringValue)) return true;

            var m = Regex.Match(stringValue);

            // looking for an exact match, not just a search hit.
            return (m.Success && (m.Index == 0) && (m.Length == stringValue.Length));
        }
    }
}

EmailAttribute Extending RegularExpressionAttribute
using System.ComponentModel.DataAnnotations;

namespace Validation
{
    /// <summary>
    /// Email Attribute class used for email validation. Used similar to the System.ComponentModel.DataAnnotations.RegularExpressionAttribute.
    /// </summary>
    public class EmailAttribute : RegularExpressionAttribute
    {
        #region Ctors

        /// <summary>
        /// Initializes a new instance of the <see cref="EmailAttribute"/> class.
        /// </summary>
        public EmailAttribute()
            : base(
                @"^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-zA-Z0-9]{1}[a-zA-Z0-9\-]{0,62}[a-zA-Z0-9]{1})|[a-zA-Z])\.)+[a-zA-Z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$"
                )
        {
        }

        #endregion Ctors
    }
}
ddd
Again, whatever implementation you choose is up to you.  Both implementations have the same resulting functionality - Email validation via data annotations.

2. Create a custom validator that extends the DataAnnotationsModelValidator
This class - the validator class - is the class that provides metadata to enable client-side validation.  This class essentially transfers settings, in our case the ErrorMessage and Pattern properties of the EmailAttribute class, from the attribute decorations on your model object to the client-side consumer.  The client-side consumer is what provides the client-side validation.

using System.Collections.Generic;
using System.Web.Mvc;
using Chinook.Framework.Validation;

namespace Chinook.Web.Core.Validation
{
    /// <summary>
    /// Provides a model validator for the EmailAttribute annotation.
    /// </summary>
    public class EmailValidator : DataAnnotationsModelValidator<EmailAttribute>
    {
        #region Fields

        private readonly string _errorMessage;
        private readonly string _pattern;

        #endregion Fields

        #region Ctors

        /// <summary>
        /// Initializes a new instance of the <see cref="EmailValidator"/> class.
        /// </summary>
        /// <param name="metadata">The metadata.</param>
        /// <param name="context">The context.</param>
        /// <param name="attribute">The attribute.</param>
        public EmailValidator(ModelMetadata metadata, ControllerContext context, EmailAttribute attribute)
            : base(metadata, context, attribute)
        {
            this._errorMessage = attribute.ErrorMessage;
            this._pattern = attribute.Pattern;
        }

        #endregion Ctors

        #region Methods

        /// <summary>
        /// Retrieves a collection of client validation rules.
        /// </summary>
        /// <returns>A collection of client validation rules.</returns>
        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            var rule = new ModelClientValidationRegexRule(this._errorMessage, this._pattern);
            return new[] {rule};
        }

        #endregion Methods
    }
}
So now that we have the attribute and validator classes created, we need to...

3. Create a client-side script to handle the client-side validation
I feel like I'm cheating on you here.  Out scenario - email validation - and it's implementation do not require us to write client-side script.  I know, I know... if you feel robbed, you can create your own client-side script, and register is with the jQuery Validate plugin.  I'll try and create a future post will another validator that requires writing client-side script and jQuery Validate registration, but for now we going to enjoy the luxuries of extending and using existing functionality.  But, HOW do we get away with using existing client-side script?

Take a look at the following code snippet from step two:
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
    var rule = new ModelClientValidationRegexRule(this._errorMessage, this._pattern);
    return new[] {rule};
}

This method 'retrieves' a collection of client validation rules.'  And since we are essentially using a RegularExpressionAttribute class (via extension), we can use the client validation rules used by the RegularExpressionAttribute's Model Validator class via the ModelClientValidationRegexRule passing our Error Message and Regular Expression pattern.

This bring us to our last step...

4. Register the attribute/validator classes in your app's bootstrapper or Global.asax
To make all this sweetness happen, we need to register the attribute and validator classes when the app domain kicks off its like cycle.  In your Global.asax's Application_Start method, register the attribue and validator classes using the following code snippet:
// register custom model validators
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAttribute), typeof(EmailValidator));
To use the attribute and validator classes use just created, you need to decorate you model/POCO classes with data annotations, including your EmailAttribute class.  The following shows how the EmailAttribute is used (along with other data annotations) to decorated a property in your model class:
[Display(Name = "Email")]
[Required(ErrorMessage = "Email is Required.")]
[Email(ErrorMessage = "Not a valid Email Address.")]
[StringLength(60, ErrorMessage = "Email must be under 60 characters.")] 
public string Email { get; set; }

Once you create strongly typed Edit and/or Create Views using your models, include the script libraries, and run the application, you will see the fruits of your labor and get client-side validation.  The screenshot below is an example.

Anyway, I hope you got a little something out of this post.

Thanks for reading...

Wednesday, November 26, 2008

jQuery Validation Plugin

This is an extension to Mohammed Nour El-Din's blog post on the jQuery Validation Plug-in; however, for ASP.NET Forms. Why Forms? Well, ASP.NET MVC is still beta and I sure most of our employers are NOT willing to support a beta product in a production environment. Therefore, until MVC is production ready and supported by the enterprise, we must make accommodations (hacks/workarounds) to support non-out-of-box scenarios. Besides, jQuery is awesome, and get this, it is now supported and encouraged by Microsoft.

Just to sure, I assume that if you are reading this blog entry you are familiar with jQuery, the jQuery Plug-in, ASP.NET, and client-side programming with ASP.NET. If not, Google the respective topic.

All of my statement's here are from my experience using the jQuery Validation Plug-in with ASP.NET. I have tried many different ways to make the plugin work with ASP.NET; however, I find that the following implementation fulfills my requirements the best.

The jQuery Validation Plug-in (and jQuery in general) follows the method of Convention over Configuration. The plug-in extensively uses and input control's name attribute by conversion so the user does not have to configure the plug-in to work; therefore, it just works. The plug-in also assumes that a control's name and id attributes are the same. In the ASP.NET world, this is not the case out-of-the-box. Out-of-the-box, a control's name attribute replaces the underscore (_) character with a US currency sign ($). The strategic reasons as to why ASP.NET implements this convention are out of scope for this post; however, you can find must more on Microsoft.com or via Google.

For these examples, I am using jQuery 1.2.6, the Validation Plug-in, the jQuery Forms Plug-in and ASP.NET 3.5. The following are my external JavaScript references:

1 <script language="javascript" type="text/javascript" src="jquery-1.2.6.pack.js">script>

2 <script language="javascript" type="text/javascript" src="jquery-validate/jquery.validate.js">script>

3 <script language="javascript" type="text/javascript" src="jquery-validate/lib/jquery.form.js">script>

The following are my styles:

1 <style type="text/css">

2 div.field-container { margin: 5px; }

3 label.labelField { width: 100px; display: block; float: left; }

4 label.error { font: bold 11px Verdana; color: Red; font-style:italic; padding-left: 10px; display: block; }

5 input[type=text], input[type=password] { border: 1px solid #7b9ebd; }

6 input:focus { border: 1px dotted black; }

7 input.error { border: 1px dotted red; }

8 style>

The following is the markup for the ASP.NET controls:

1 <div class="field-container">

2 <label class="labelField">

3 <span>First Name:span>

4 label>

5 <asp:TextBox ID="txtFirstName" runat="server" />

6 div>

7

8 <div class="field-container">

9 <label class="labelField">

10 <span>Last Name:span>

11 label>

12 <asp:TextBox runat="server" ID="txtLastName" />

13 div>

14

15 <div class="field-container">

16 <label class="labelField">

17 <span>User Name:span>

18 label>

19 <asp:TextBox runat="server" ID="txtUserName" />

20 div>

21

22 <div class="field-container">

23 <label class="labelField">

24 <span>Email:span>

25 label>

26 <asp:TextBox runat="server" ID="txtEmail" />

27 div>

28

29 <div class="field-container">

30 <label class="labelField">

31 <span>Password:span>

32 label>

33 <asp:TextBox runat="server" ID="txtPassword" TextMode="Password" />

34 div>

35

36 <div class="field-container">

37 <label class="labelField">

38 <span>Web Site:span>

39 label>

40 <asp:TextBox runat="server" ID="txtWebsite" class="url" />

41 div>

Okay, now to the meat. The following is the JavaScript code that I use to create, add, and extend the Validate Plug-in:

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

2 // object containing asp.net control ids

3 var _pageFields = {

4 form: '<%= Master.FormMaster.ClientID %>',

5 firstName: '<%= txtFirstName.ClientID %>',

6 lastName: '<%= txtLastName.ClientID %>',

7 userName: '<%= txtUserName.ClientID %>',

8 email: '<%= txtEmail.ClientID %>',

9 password: '<%= txtPassword.ClientID %>',

10 webSite: '<%= txtWebsite.ClientID %>'

11 };

12

13 $(document).ready(function() {

14 var addOption = function(selector, /* jQuery Selector */

15 isRule /* true: rule | false: message */,

16 optionObject, /* json-formmated object to pass to the validate jQuery extension */

17 optionsToAdd /* rule or message options object*/

18 ) {

19 var $element = $(selector);

20 if (!$element[0]) return;

21 var key = (!!isRule) ? 'rules' : 'messages';

22 optionObject[key][$element.attr('name')] = optionsToAdd;

23 };

24

25 // temp options object for use with validate jQuery extension

26 var options = { rules: {}, messages: {} };

27

28 // add first name options to options object

29 addOption('#' + _pageFields.firstName, true, options, {

30 required: true,

31 minlength: 2

32 });

33 addOption('#' + _pageFields.firstName, false, options, {

34 required: 'Please enter a First Name.',

35 minlength: 'Your First Name must consist of at least 2 characters.'

36 });

37

38 // add last name options to options object

39 addOption('#' + _pageFields.lastName, true, options, {

40 required: true,

41 minlength: 2

42 });

43 addOption('#' + _pageFields.lastName, false, options, {

44 required: 'Please enter a Last Name.',

45 minlength: 'Your Last Name must consist of at least 2 characters.'

46 });

47

48 // add user name options to options object

49 addOption('#' + _pageFields.userName, true, options, {

50 required: true, minlength: 2

51 });

52 addOption('#' + _pageFields.userName, false, options, {

53 required: 'Please enter a User Name.',

54 minlength: 'Your User Name must consist of at least 2 characters.'

55 });

56

57 // add email options to options object

58 addOption('#' + _pageFields.email, true, options, {

59 required: true,

60 minlength: 2

61 });

62 addOption('#' + _pageFields.email, false, options, {

63 required: 'Please enter an Email.',

64 minlength: 'Your Email must consist of at least 2 characters.'

65 });

66

67 // add password options to options object

68 addOption('#' + _pageFields.password, true, options, {

69 required: true,

70 minlength: 8

71 });

72 addOption('#' + _pageFields.password, false, options, {

73 required: 'Please enter a Password.',

74 minlength: 'Your Password must consist of at least 8 characters.'

75 });

76

77 // add website options to options object

78 addOption('#' + _pageFields.webSite, true, options, {

79 url: true

80 });

81

82 $('#' + _pageFields.form).validate(options);

83 });

84 script>

Okay, so all I am doing here is creating a client-side object that contains the ASP.NET generated client IDs for the HTML input controls (lines: 1 - 11). I know, it's ugly, but it's what I find to be the best implementation.

Then I use jQuery's 'When the DOM is ready, do this...' function - $(document).ready(function(){}); (starting line: 13).

Within the anonymous function passed to ready, I create a new function 'addOption' (line: 14), As you can see from the code, there are four function parameters. The parameters are self-documented; however, I have also include in-line comments. All that t function does is add a rule or message referencing the respective jQuery wrapped selector object as the rule or message object key. Again, I assume that you are familiar with the jQuery Validation Plug-in and the members/properties of the rules and messages option object. WHAT did he say? Check it out the home page: jQuery plugin: Validation and documentation: Plugins/Validation.

Okay, so I describe the process of using ASP.NET Forms controls with the jQuery Validation Plug-in using a very code-centric strategy. Sometimes you must experiment and experience the discomforts before you can actually get ASP.NET to cooperate with jQuery; however, don't let this discourage you. jQuery is here to stay and it is a very powerful JavaScript library.

BTW, I am using the awesome Visual Studio Plug-in CopySourceAsHtml (CSAH) to copy my source from Visual Studio to my HTML editior as HTML formatted markup. Check it out...

My intent was to write this blog entry so that I don't loose my finding somewhere on my hard drive. I hope these efforts help someone else out there. Thanks for reading...