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:
function CustomValidationMethod(condition, eventArgs)
{
}
The condition and eventArgs variables contain the following properties:
condition.controlToValidate
condition.errorMessageTarget
condition.errorMessage
condition.showInSummary
condition.clientSideFunction
eventArgs.isValid
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:
[assembly: WebResource("DefaultN.Web.Validation.Validation.js", "text/javascript")]
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:
<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:
protected internal abstract bool SettingsAreValid { get;}
public abstract string DefaultClientFunction {get;}
public abstract bool Validate();
public abstract BaseCondition Clone();
protected internal abstract void WriteJavascriptProperties(Dictionary<string, string> properties);
protected internal abstract void WriteDesignerProperties(Dictionary<string, string> properties);
For example, here is the code for the ChangeRequiredCondition:
public class ChangeRequiredCondition : BaseCondition
{
#region Protected Member Variables
protected string m_InitialValue;
#endregion
#region Properties
[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
public ChangeRequiredCondition()
: base()
{
m_InitialValue = String.Empty;
}
#endregion
#region Overrides
protected internal override bool SettingsAreValid
{
get { return m_ControlToValidate.Length > 0; }
}
public override bool Validate()
{
if (m_ControlToValidate == null)
throw new NullReferenceException("The ControlToValidate property must be set.");
return ControlValue != m_InitialValue;
}
public override string DefaultClientFunction
{
get { return "validator.validateChangeRequiredCondition"; }
}
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;
}
protected internal override void WriteJavascriptProperties(Dictionary<string, string> properties)
{
properties.Add("initialValue", m_InitialValue);
}
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:
public string ControlToValidateClientID {get;}
public string ErrorMessageTargetClientID {get;}
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.
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.