Click here to Skip to main content
15,886,199 members
Articles / Web Development / HTML
Article

Single Control Validation Solution

Rate me:
Please Sign up or sign in to vote.
2.50/5 (4 votes)
12 Dec 20068 min read 37.5K   209   25   9
Describes a control that wraps web form validation in to a single control.

Sample Image - Article.jpg

Introduction

I've been playing with ASP.NET since Beta 1, and I have to admit that I love it! What used to take days in traditional ASP can now be done in an afternoon. One of the few complaints that I have is ASP.NET's implementation of validators. While I certainly believe that validation controls extremely are time saving, I'm not really thrilled with the ones provided by Microsoft. They work great if you only have a few items to validate, but can become a chore to maintain, when there are many fields that need validation.

What I wanted was a single control that you could drop on to your form, that would handle all the validation for you. This way, you only have a single validation control to maintain.  Simply add the control to your your form, then edit the Conditions property.

The control was originally published here on CodeProject back in September of 2002, and has since gone through some major changes.  First, it has been rewritten from the ground up in C# (it was in VB.NET the first time around), and supports the .NET 2.0 Framework (if you are developing 1.1 sites, you'll need the original version).  The previous version also required you to place Javascript files in to the _vti_script directory.  This is no longer necessary.  The Javascript for the control is now maintained in a single embedded file, exposed via the WebResource assembly attribute.

Conditions

The Validator control comes with several built in validators.  These mirror the validators provided by the default ASP.NET controls, and provide additional ones as well.

ChangeRequiredCondition - Mirrors the RequiredValidator.  The value of the targeted control must be changed from a specific value, which by default, is String.Empty.

RegularExpressionCondition - Mirrors the RegularExpressionValidator.  The value of the targeted control must match the given regular expression pattern.

RangeCondition - Mirrors the RangeValidator.  The value of the targeted control must fall within a specific range.

ComparisonCondition - Mirrors the ComparisonValidator.  The value of the targeted control must compare in the defined way to another control or value.

CustomCondition - Mirrors the CustomValidator.  The value of the targeted control must validate against custom logic.

ModulusCondition - The value of the targeted control must divide evenly by a specific modulus.

AjaxCondition - The value of the targeted control is validated on the server via AJAX.

Using the code

For the most part, using the control is fairly straight forward. Like any other control, the easiest way to use it is to add it to your VS.NET Web Forms toolbox by right-clicking in the toolbox, choosing 'Choose Items' from the menu, and locating the DefaultN.Web.Validator.dll library.  You then drag an instance of the control on to your form, and VS.NET will automatically set up the proper references and the @Register page tag.

When adding a CustomCondition or AjaxCondition to the control's Conditions property, you need to specify a few additional things beyond what the other conditions require.  To use the CustomCondition, like the ASP.NET provided CustomValidator, you need to create a client-side javascript function to handle the validation.  The control will expect a function with the following signature:

JavaScript
function CustomValidationMethod(condition, eventArgs)
{   
}

The condition and eventArgs variables contain the following properties:

jscrip
condition.controlToValidate // The unique ID of the control to validate
condition.errorMessageTarget // The unique ID of the control to optionally set with this controls error message
condition.errorMessage // The error message to display when the condition fails
condition.showInSummary // True if the condition will be shown in the summary
condition.clientSideFunction // The name of the client side validation function

eventArgs.isValid // Self explanatory

The server side function for the CustomCondition object looks like this:

public bool ValidateSomeCustomCondition(CustomCondition condition)
{
}

The AjaxCondition works in a similar way, except that you don't write a client side script, only the server side function, which looks the same as the CustomValidation function, but takes an AjaxCondition object as its parameter instead of a CustomCondition object.

The Validator object itself has 2 additional members that may be of interest.  First, a ConditionsFailed event is raised on the server side if validation fails on the server.  You can then check the FailedConditions collection, and process this however you see fit.

How it works

The Validator control works similarly to any other ASP.NET validator.  It derives from System.Web.UI.WebControl, and implements the System.Web.UI.IValidator interface.  What makes it differrent, of course, is that it can validate any number of controls on the page, not just one, and displays it's own summary.  To do this, the control uses several ASP.NET and VS.NET classes to interact with the designer as well as the page during run time.

Embedded Javascript File

In the original version of this control, you had to place 3 javascript files in the _vti_script directory of your IIS root.  With the current version, this is not necessary.  ASP.NET 2.0 provides the WebResource attribute, which allows you to expose an embedded resource as an available file to your site.  The current version embeds a single .js file, called Validation.js, and exposes it using the WebResource attribute.  So now, instead of having to move .js files, all you need is the DefaultN.Web.Validator.dll file.

If you're unfamiliar with the WebResource attribute, here's how it's used (more information can be found here[^]).  First, you need to set up your resource.  Do this by adding it to your project if it isn't already.  Then set the 'Build Action' property of that resource to 'Embedded Resource'.  The full name of this file compiled in to your assembly will be the default namespace of your project, plus the name of the file.  In the case of the Validator control, my default namespace is DefaultN.Web.Validation, and the .js file is Validation.js.  Therefore, the full name of the file will be DefaultN.Web.Validation.Validation.js.  Whew!  Next, the code to actually use this file at the client:

C#
// AssemblyInfo.cs
// The first parameter is the FULL NAME of the compiled resource.
// The second parameter is the MIME type.  So text/javascript, or image/jpeg, for example.
[assembly: WebResource("DefaultN.Web.Validation.Validation.js", "text/javascript")]

// In the Load or Init event of your page or control...
// Here, we pass to RegisterClientScriptInclude the type that owns the script (usually the page or control),
//   a key to identify this script block to ASP.NET (this can be anything: "Validation.js", "Bob", whatever),
//   and the return value from GetWebResourceUrl, which is the URL to the resource.  GetWebResourceUrl in turn
//   takes the owner of the script, and the FULL NAME of the resource desired.
Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "Validation.js",
   Page.ClientScript.GetWebResourceUrl(this.GetType(), "DefaultN.Web.Validation.Validation.js"));

The end result of this call will look something like the following:

HTML
<script src="/DefaultNWebSite/WebResource.axd?d=X3g8zd4Bo6mVbyhGZW7_YOuNIu7_X-jeI-Sdbg4xYG6HoUIbzhQiNCjwNT4fGWwt3Wb2J2SuqKQUsiWLNKDcbA2&t=633014129061718750"
    type="text/javascript"></script>

As you can see, it's not a URL that can be easily remembered.  However, the ASP.NET engine provides the mechanisms to make this a valid copy of your embedded resource, or in this case, the javascript file that makes client side scripting possible for the Validator control.

Interaction with the page

The Validator control is capable of validating any control on the page that is validatable.  However, due to the workings of the ASP.NET Master Page framework, it cannot validate controls that are a part of the master page.  At least not directly.  Here's why.  When your .aspx page is rendered, the master page actually becomes another child of the Page object.  But at design time, the master page is in control.  Because of this, aquiring a proper ID is not possible (at least, I havn't figured out how to do so yet ).  However, you CAN still validate controls on a master page.  If you use one of the standard .NET validation controls on the master page control, the Validation control here will attach to it, and block post back if it isn't valid.  It will even display the Master Page validator error messages in its summary.

Conditions

Each condition object is derived from the base class BaseCondition, and must implement several abstract members:

C#
// When editing the conditions in the Conditions collection at design time,
// this property returns true if the condition has been set up correctly for
// run time (ControlToValidate has a valid value, the RegularExpression property
// for the RegularExpressionCondition has been set, etc.).
protected internal abstract bool SettingsAreValid { get;}

// This is the name of the function that will be called client side (javascript)
// if one is not specifically provided to the ClientSideFunction property of the
// condition (validator.validateChangeRequiredCondition, for example).
public abstract string DefaultClientFunction  {get;}

// This method is called to actually validate the condition.
public abstract bool Validate();

// This method clones the condition.
public abstract BaseCondition Clone();

// This method provides the Validator control with the condition specific properties to write to javascript,
// and takes a Dictionary of keyed values.  You only need to provide those properties that are specific
// to your validator.  For example:
// protected internal override void WriteJavascriptProperties(Dictionary<string, string> properties)
// {
//    properties.Add("initialValue", this.InitialValue);
// }
protected internal abstract void WriteJavascriptProperties(Dictionary<string, string> properties);

// This method provides the Validator control with the condition specific properties to persist via the designer,
// and works in the same way as the WriteJavascriptProperties method.  Example:
// protected internal override void WriteJavascriptProperties(Dictionary<string, string> properties)
// {
//    properties.Add("InitialValue", this.InitialValue);
// }
protected internal abstract void WriteDesignerProperties(Dictionary<string, string> properties);

For example, here is the code for the ChangeRequiredCondition:

C#
/// <summary>
/// Validates that the attached control has had its value changed from a default value.
/// </summary>
public class ChangeRequiredCondition : BaseCondition
{
    #region Protected Member Variables
    /// <summary>
    /// The initial value that this conditions targeted control must be changed from to be considered valid.
    /// </summary>
    protected string m_InitialValue;
    #endregion

    #region Properties
    /// <summary>
    /// The initial value that this conditions targeted control must be changed from to be considered valid.
    /// </summary>
    [Description("The initial value that this conditions targeted control must be changed from to be considered valid.")]
    [Category("Initial Value")]
    public string InitialValue
    {
        get { return m_InitialValue; }
        set { m_InitialValue = value; }
    }
    #endregion

    #region Construction
    /// <summary>
    /// Constructs a new instance of the ChangeRequiredCondition class.
    /// </summary>
    public ChangeRequiredCondition()
        : base()
    {
        m_InitialValue = String.Empty;
    }
    #endregion

    #region Overrides
    /// <summary>
    /// Lets the Validator control know if this condition has been set up properly.
    /// </summary>
    protected internal override bool SettingsAreValid
    {
        get { return m_ControlToValidate.Length > 0; }
    }

    /// <summary>
    /// Determines whether this condition has been met or not.
    /// </summary>
    public override bool Validate()
    {
        if (m_ControlToValidate == null)
            throw new NullReferenceException("The ControlToValidate property must be set.");

        return ControlValue != m_InitialValue;
    }

    /// <summary>
    /// Gets the default client function to call for validation.
    /// </summary>
    public override string DefaultClientFunction
    {
        get { return "validator.validateChangeRequiredCondition"; }
    }

    /// <summary>
    /// Clones the condition to a new condition.
    /// </summary>
    /// <returns>The cloned condition.</returns>
    public override BaseCondition Clone()
    {
        ChangeRequiredCondition newCondition = new ChangeRequiredCondition();
        newCondition.ControlToValidate = this.ControlToValidate;
        newCondition.ErrorMessageTarget = this.ErrorMessageTarget;
        newCondition.ErrorMessage = this.ErrorMessage;
        newCondition.ShowInSummary = this.ShowInSummary;
        newCondition.ClientSideFunction = this.ClientSideFunction;
        newCondition.InitialValue = this.InitialValue;

        return newCondition;
    }

    /// <summary>
    /// Called when the validator control writes out the conditions to javascript.  Only write those properties specific to the derived condition, and not provided by the BaseCondition class.
    /// </summary>
    /// <param name="properties">The collection of properties that will be written to the javascript conditions collection.</param>
    protected internal override void WriteJavascriptProperties(Dictionary<string, string> properties)
    {
        properties.Add("initialValue", m_InitialValue);
    }

    /// <summary>
    /// Called when the validator control's designer persists the collection of conditions to the ASP.NET page.
    /// </summary>
    /// <param name="properties">The collection of properties that will be written to the ASP.NET page.</param>
    protected internal override void WriteDesignerProperties(Dictionary<string, string> properties)
    {
        properties.Add("InitialValue", m_InitialValue);
    }
    #endregion
}

The BaseValidator also provides several utility properties that can be used:

C#
// Gets the ASP.NET mangled name of the control at runtime.
// For example: ctl00_UserControl1_TextBox1
public string ControlToValidateClientID {get;}

// Gets the ASP.NET mangled name of the error message target control at runtime.
public string ErrorMessageTargetClientID {get;}

// Gets the value of the ControlToValidate control, using Reflection to get the value,
// meaning that it will retrieve the value regardless of whether the control has a Text,
// Value, or SelectedValue property.
public string ControlValue {get;}

Javascript

On the client side, the Validation.js file contains the logic for each of the built in conditions, and is wrapped in the validator object via JSON (Javascript Object Notation) syntax.  The div, properties, and conditions properties are set at run time by the validator control, and reflect the id of the div panel that represents the validator at run time, the runtime properties of the validator control, and the collection of conditions specified by the developer, respectively.

Besides the validator control logic itself, the Validation.js file also extends the string class by adding trimming, conversion to number and date values, and verification that a value is a number or datetime (isInteger, isDateTime, etc.).  It also expands the Array class by adding an indexOf method (VERY useful).

Future Changes

The DefaultN.Web.Validation.Validator control has taken about 6 months to build from the ground up, since I didn't start with the code base of the first version, and has been a lot of fun to write.  It's also a continually evolving entity, and will get updated and reposted as I make further changes.  Here's a brief list of some things I'd like to add in the future:

SummationCondition - This condition will verify that a group of text fields all add up to some defined value.  For example, a group of fields that must add up to 100%.

GroupContentCondition - Attaches to a group of text fields, and verifies that at least one of them has content.

GroupCheckCondition - Verifies that at least X number of checkboxes have been checked (useful for survey or preference screens, for example).

Error Indicators - The Validator control will display a bullet or user defined image/charector next to invalid fields.

And of course, if anybody has any suggestions, bug reports, or other commens they'd like to make, please feel free to do so.

History

August, 2002

  • Version 1 released.  Written in VB.NET, and worked with .NET 1.0 and up.

December, 2006

  • Version 2 released.  Written from ground up in C#, and targeted to the .NET 2.0 Framework.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect
United States United States
I'm a Software Architect, working for a telecom in Portland, OR. My specialties are C#, Win32 C/C++ programming, Visual Basic (6 and .NET), and ASP(.NET), XML/XSL, and database programming. I'm currently learning F#. Completely different than anything I've ever done, but very cool. I'm married to the most wonderful woman, have a beautiful daughter that I'm very proud of, and a step-son that's rocking in high school. I'm also a 2nd degree blackbelt in Olympic style (WTF) Taekwondo, and have trained in Brazilian Jiujitsu, Jeet Kun Do, Krav Maga.

Comments and Discussions

 
QuestionRe Ajax condition.. Pin
npalsingh00126-Jul-07 15:10
npalsingh00126-Jul-07 15:10 
AnswerRe: Re Ajax condition.. Pin
Jamie Nordmeyer26-Jul-07 17:29
Jamie Nordmeyer26-Jul-07 17:29 
AnswerRe: Re Ajax condition.. Pin
Jamie Nordmeyer5-Aug-07 15:26
Jamie Nordmeyer5-Aug-07 15:26 
GeneralGood Job Pin
anichin16-Apr-07 10:45
anichin16-Apr-07 10:45 
GeneralRe: Good Job Pin
Jamie Nordmeyer16-Apr-07 11:43
Jamie Nordmeyer16-Apr-07 11:43 
GeneralGreat idea Pin
Simone Busoli13-Dec-06 2:15
Simone Busoli13-Dec-06 2:15 
GeneralRe: Great idea Pin
Jamie Nordmeyer13-Dec-06 5:32
Jamie Nordmeyer13-Dec-06 5:32 
GeneralSuggestion Pin
digiduck12-Dec-06 7:23
digiduck12-Dec-06 7:23 
GeneralRe: Suggestion Pin
Jamie Nordmeyer12-Dec-06 7:53
Jamie Nordmeyer12-Dec-06 7:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.