ASP.NET Validation in Depth

 

Anthony Moore
Microsoft Corporation

October 2000
Updated March 2002

Summary: A detailed examination of the workings of ASP.NET validation Web controls. (15 printed pages)

Contents

Introduction
In the Beginning
What Happens When?
Server-Side Validation Sequence
Client-Side Validation
Validity Rules and Meaningful Error Messages
Effect of Enabled, Visible, and Display Properties
The CustomValidator Control
Which Controls Can Be Validated?
That's It

Introduction

This article discusses in detail how the ASP.NET validation controls work. This article is recommended reading for anyone building complex pages with validation controls or those looking to extend the validation framework. For people looking to get started with the validation controls or deciding whether to use them, see User Input Validation in ASP.NET.

In the Beginning

Throughout the development of ASP.NET, we knew that helping out with validation was important. Take a look at most commercial Web sites today and you will notice that they are filled with forms that clearly execute a lot of handwritten code to perform validation. Validation code is not particularly sexy to write. It can be exciting to write code to display tables of data or to dynamically generate charts, but no one gets their coworkers to check out the cool way they stopped someone from entering a blank value for a name field.

Validation of Web applications is particularly frustrating for other reasons as well. HTML 3.2 is so limited in what you can control and what feedback you get from the user that you can't apply the same tricks you can use on a richer client, such as preventing the user from entering certain characters, or making beep sounds. It is possible to create more powerful validation using browser script. This can be hard to justify, however, because script is not always present in client browsers and can be bypassed by malicious users. It is necessary, therefore, to implement the same checks on the server anyway, in order to have a secure site.

In the development of ASP.NET, our original intention was to have just one control to handle validation, which would have been a version of the TextBox control that could also display errors. When the time came to design the control, however, it became clear that this would not cut the mustard. We looked at a large number of data-entry forms and tried to find a solution that would fit as many of them as possible. We found a number of interesting things about data-entry forms:

  • It is common to have error messages or icons adjacent to input elements, but they are almost always positioned in different table cells.
  • It is common to have an area of the page where all errors are summarized.
  • Many sites contain client-side script to provide more immediate feedback and prevent wasted round-trips to the server.
  • Many sites that have client-side script display message boxes if there were errors.
  • It is not just text inputs that are validated. Drop-down lists and radio buttons are also frequently validated.
  • When a field is empty, sites generally display a message or icon that is different from the one they display when the entry is invalid.
  • Many validation checks are excellent candidates for regular expressions.
  • It is common for validity to be dependent on comparisons between inputs.
  • Among validation tasks, 90 percent or more are common operations like checking names and ZIP codes. Most sites seemed to be reinventing the wheel.
  • There is generally too much variation between sites to have a perfect solution that can do 100 percent of validation tasks for every site.

Consideration of all these points led to the eventual solution of the five Validator controls, the ValidationSummary control, and integration with the Page object. It was also clear that the solution needed to be extensible, and there needed to be an API for working with it on both the client and the server.

When we looked at the different sorts of validation that takes place, it seemed like we would need a bigger toolbox. In most component environments like Microsoft® ActiveX®, we probably would have tried to overload the functionality of all the validation controls into one control that worked with different properties in different modes. However, thanks to the magic of inheritance in the Microsoft® .NET Framework, it is possible to provide a suite of controls that do specific validation with specific properties, because the overhead of deriving each new control is very small.

Most of the work done by these controls is implemented in their common parent, BaseValidator. You can also derive from BaseValidator or the other controls to take advantage of this. In fact, even BaseValidator is too lazy to implement its own Text property and inherits from Label.

What Happens When?

It is beneficial to understand the sequence of events when a page with validation Web controls is processed. If any of the validation conditions are optional, you will want to know exactly when validation takes place on both the client and the server. If you are writing your own validation routines that are potentially time-consuming or that have side effects, it is also important to have an idea of when they will be called.

First, let's look at the server.

Server-Side Validation Sequence

It is important to understand the life cycle of a page. For those used to working with forms in Visual Basic or similar rich client tools, it takes a bit of getting used to. A page and all the objects on it do not actually live for as long as a user is interacting with them, although it can sometimes seem like they do.

Here is a simplified sequence of events when a page is first accessed:

  1. Page and its controls are created, based on ASPX file.
  2. Page_Load event fires.
  3. Page and control properties are saved to a hidden field.
  4. Page and controls are turned into HTML.
  5. Everything is thrown away.

Now, when a user clicks on a button or similar control, it goes back to the server and does a similar sequence. This is called the post-back sequence:

  1. Page and its controls are created based on ASPX file.
  2. Page and control properties are recovered from hidden field.
  3. Page controls are updated based on user input.
  4. Page_Load event fires.
  5. Change notification events fire.
  6. Page and control properties are saved to a hidden field.
  7. Page and controls are turned into HTML.
  8. Everything is thrown away again.

Why don't we just keep all objects in memory? Because Web sites build with ASP.NET would not work with very large numbers of users. This way, the only objects in memory on the server are things being processed right now.

When does server-side validation take place? Well, it does not take place at all on the first page fetch. Most of our end-users are very diligent, and we want to give them the benefit of the doubt that they will fill in the form correctly before we bombard them with red text.

On the post-back, validation takes place during step 5, just before the event fires for the button or control that triggered the validation. Button controls in ASP.NET have a property called CausesValidation that defaults to True. It is the action of clicking on buttons that makes validation happen. The best place to check the results of validation is in the event handler that triggered the validation. You can also have buttons with CausesValidation=False, that will not cause any validators to be evaluated.

One potentially confusing thing about this timing is that the validators will not have been evaluated at the time Page_Load is triggered. The benefit of this is that it gives you a chance to programmatically change property values affecting the validity of the page, such as enabling or disabling certain validators.

If this timing is not to your liking and you prefer to evaluate everything in Page_Load, you can do this by explicitly triggering the validation during this event by calling Page.Validate. After this has been called you can then check the result of Page.IsValid. If you try to query the result of Page.IsValid before Page.Validate has been called, either explicitly or being triggered by a button with CausesValidation=True, then its value is meaningless, so an exception will be thrown.

The Page API

The Page object has some important properties and methods with respect to server-side validation. They are summarized in Table 1:

Table 1. Page object properties and methods

Property or Method Description
IsValid property This is the most useful property. It allows you to check whether the entire form is valid. This is generally done before performing a database update. It is true only if all the objects in the Validators collection are valid, and it does not cache this value.
Validators property A collection of all the validation objects for the page. It is a collection of objects that implement the IValidator interface.
Validate method A method that is called at validation time. The default implementation on Page goes to each validator and asks it to evaluate itself.

The Validators collection is useful for a number of things. It is a collection of objects of that implement the IValidator interface. I use the term objects rather than controls because the Page cares only about the IValidator interface. While it happens that all the validators will generally be visual controls that implement IValidator, there is no reason someone cannot come along with an arbitrary validation object and add it to the page.

The IValidator interface has the following properties and methods:

Table 2. IValidator interface properties and methods

Property or Method Description
IsValid property Indicates whether or not the validation check made by the individual validating object has passed. You can manually alter this value after validation has taken place.
ErrorMessage property Describes the error that the validation object is validating and that may be displayed to the user.
Validate method Executes the validation check of the validation object to update its IsValid value.

You can do some interesting things with this interface. For example, to reset the page to a valid state, use the following code (examples shown in C#):

    IValidator val;
    foreach(val in Validators) {
        Val.IsValid = true;
    }

To re-execute the whole validation sequence, use the following code:

    IValidator val;
    foreach(val in Validators) {
        Val.Validate();
    }

This is equivalent to calling the Validate() method on Page.

Another way to make some changes before validation takes place is to override the Validate method. This example shows a page that contains a validator that is turned on or off based on the value of a checkbox:

    public class Conditional : Page {
        public HtmlInputCheckBox chkSameAs;
        public RequiredFieldValidator rfvalShipAddress;

        public override void Validate() {
            // Only check ship address if not same as billing
            bool enableShip = !chkSameAs.Checked;
            rfvalShipAddress.Enabled = enableShip;
            // Now perform validation
            base.Validate();
        }
    }

Client-Side Validation

If client-side validation is enabled for your page, a whole different sequence occurs in-between the round trips. Client-side validation works using client JScript®. No binary components are needed to make it work.

While the JScript language is reasonably well standardized, the Document Object Model (DOM) for interacting with HTML documents in browsers did not have a widely adopted standard at the time these components were developed and tested. As a result, client-side validation only takes place in Internet Explorer 4.0 and later, because it targets the Internet Explorer DOM.

From a server point of view, client-side validation just means that the validation controls emit different stuff into the HTML. Other than that, their sequence of events is exactly the same. The server-side checks are still carried out. This may seem redundant, but it is important because of the following:

  • Some validation controls may not support client scripting. A good example: if you are using a CustomValidator with a server validation function but no client validation function.
  • Security considerations. Someone can very easily take a page with script and disable or change it. You should not rely on script to stop bad data getting into to your system, only to provide more immediate feedback to your users. For this reason, if you are using a CustomValidator, you should not provide a client validation function without a corresponding server validation function.

Every validation control makes sure that a standard block of client script is emitted into the page. This is actually just a small amount of script that includes a reference to code in a script library called WebUIValidation.js. This file, which is downloaded separately and can be cached by the browser, contains all of logic for client-side validation.

About the Script Library

Because the validation Web controls script that is in a script library, it is not necessary to emit all of the code for client-side validation directly into the page, although it acts as though this is what has happened. The main script file reference looks like this:

<script language="javascript" 
        src="/aspnet_client/system_web/1_0_3617_0/WebUIValidation.js">
</script>

By default, the script file will be installed into your default root in the aspnet_client directory, and it is referenced using a root-relative script include directive, which begins with the forward slash. This reference means that each individual project does not have to include the script library inside it, and all pages on the same machine can reference the same file. You will notice that it also has the common language runtime version number in the path, so that different versions of the runtime can run on the same machine.

If you take a look around your default virtual root, you can find this file and take a look inside it. The location of these files is specified in the machine.config file, an XML file used for most ASP.NET settings. Here is the definition of the location within that file:

        <webControls
            clientScriptsLocation="/aspnet_client/{0}/{1}/"
        />

You are encouraged to read the script to see more of what is going on. However, it is not recommended that you modify these scripts, because their function is very closely tied to a particular version of the run time. If the run time is updated, the scripts may need a corresponding update, and you will have to either lose your changes or face problems with the scripts not working. If you must change the scripts for a particular project, take a copy of the files and point your project to them by overriding the location of the files with a private web.config file.It is perfectly fine to change this location to be a relative or absolute reference.

Disabling Client-Side Validation

There are some cases where you may not want client-side validation. If the number of input fields is very small, client-side validation may not be of much benefit. You may have logic that needs a round trip to the server every time anyway. You may find that the dynamically appearing messages on the client have an adverse affect on your layout.

Note The way to disable client-side validation is to set the EnableClientScript property of the validator or ValidationSummary control to False. It is possible to have a mixture of server-only and client-sever validation components on the same page.

The Client-Side Sequence

This is the sequence of events when a page with client-side validation runs:

  1. As the page is loaded into the browser, there is some initialization done on each validation control. The controls are emitted as <span> tags with HTML attributes that correspond closely to their properties on the server. The most important thing that happens here is that any input elements referenced by the validators are "hooked up." The referenced input elements have their client events modified so that the validation routines are called whenever the input is changed.
  2. Code in the script library will be executed as users tab from field to field. The validation conditions are re-evaluated when a dependent field is changed, and the validator is made visible or invisible as appropriate.
  3. When the user pushes a button that has the CausesValidation property set to True, the validators are all re-evaluated. If they are all valid, then the form is posted to the server. If there are one or more errors, a number of things happen:
    • The submit is cancelled. The form does not get posted back to the server.
    • Any invalid validators become visible.
    • If there is a validation summary with ShowSummary=true, it will collect all the errors from the validation controls and update its contents with them.
    • If there is a validation summary with ShowMessageBox=true, it will collect the errors and display them in client message box.

Because they are executed whenever the inputs change as well as at submit time, client side validation controls generally evaluate two or more times on client. Remember that they will still be re-evaluated on the server once the submit takes place.

The Client-Side API

There is a mini-API that you can use on the client to achieve various effects with your own client-side code. Because it is not possible to make certain routines hidden, you can theoretically make use of any of the variables, attributes, and functions defined by client-side validation script. However, many of them are implementation details that may be changed. Here is a summary of the client-side objects that we encourage you to use.

Table 3. Client-side objects

Name Type Description
Page_IsValid Boolean variable Indicates whether the page is currently valid. The validation scripts keep this up to date at all times.
Page_Validators Array of elements This is an array containing all of the validators on the page.
Page_ValidationActive Boolean variable Indicates whether validation should take place. Set this variable to False to turn off validation programmatically.
isvalid Boolean property This is a property on each client validator indicating whether it is currently valid.

Bypassing Client Validation

A common task you may need to do is to have a "Cancel" button or a navigation button on a page. In this case, set the CausesValidation property on the button to False and no validation will take place, either on the server or the client. If you lay out a page like this, you will want to check Page.IsValid in your button even handlers. If you instead call Page.Validate during Page_Load, you will not have a way of knowing whether a submit or cancel button was pushed.

Special Effects

Another common requirement is to have effects other than the error messages displayed by the validators themselves in error situations. In this case, any modifications in behavior you make need to be made on both the server and the client. Suppose you want to have a Label that changes color depending on whether an input is valid. Here is how you would do this on the server:

public class ChangeColorPage : Page {
    public Label lblZip;
    public RegularExpressionValidator valZip;
    
protected override void OnLoad(EventArgs e) {     
    Page.Validate();       
        lblZip.ForeColor = valZip.IsValid? Color.Black : Color.Red;
    }               
}

This is all very nice, but whenever you modify validation like this, you may find that it looks inconsistent unless you do an equivalent operation on the client. The validation frameworks saves you from a lot of this double effort, but for extra effects you just have to do it in two places. Here is a client fragment that does the same thing:

<asp:Label id=lblZip runat=server 
   Text="Zip Code:"/> 
<asp:TextBox id=txtZip runat=server 
   OnChange="txtZipOnChange();" /></asp:TextBox><br>
<asp:RegularExpressionValidator id=valZip runat=server
   ControlToValidate=txtZip
   ErrorMessage="Invalid Zip Code" 
   ValidationExpression="[0-9]{5}" /><br>

<script language=javascript>
function txtZipOnChange() {
   // Do nothing if client validation is not active
   if (typeof(Page_Validators) == "undefined")  return;
   // Change the color of the label
   lblZip.style.color = valZip.isvalid ? "Black" : "Red";
}
</script>

Client-Side APIs

Some additional scenarios are enabled by functions that can be called from your client-side script.

Table 4. Functions called from client-side script

Name Description
ValidatorValidate(val) Takes a client-validator as input. Makes the validator check its input and update its display.
ValidatorEnable(val, enable) Takes a client-validator and a Boolean value. Enables or disables a client validator. Being disabled will stop it from evaluating and it will always appear valid.
ValidatorHookupControl(control, val) Takes an input HTML element and a client-validator. Modifies or creates the element's change event so that it updates the validator when changed. This can be useful for custom validators that depend on multiple input values.

Of particular use is to be able to enable or disable validators. If you have validation that you want active only in certain scenarios, you may need to change the activation on both server and client, or you will find that the user cannot submit the page.

Here is the previous example with a field that should only be validated when a check box is unchecked:

    public class Conditional : Page {
        public HtmlInputCheckBox chkSameAs;
        public RequiredFieldValidator rfvalShipAddress;
        public override void Validate() {
            bool enableShip = !chkSameAs.Checked;
            rfvalShipAddress.Enabled = enableShip;
            base.Validate();
        }
    }

Here is the client-side equivalent:

<input type=checkbox runat=server id=chkSameAs 
   onclick="OnChangeSameAs();" >Same as Billing<br>
<script language=javascript>
function OnChangeSameAs() {
    var enableShip = !event.srcElement.status;
    ValidatorEnable(rfvalShipAddress, enableShip);
}
</script>

Validity Rules and Meaningful Error Messages

Each validator displays a specific error message about a specific condition on a specific control. There are rules as to what is considered valid that may at first seem confusing to you as a developer, but they are necessary to allow you to construct error messages that are actually helpful to the user.

All of the validators (except for RequiredFieldValidator) are considered valid if they are blank. If a blank value is not valid, you generally need to provide a RequiredFieldValidator in addition to another validator. You need to do this because almost universally you want different error messages for the blankness and for the validity. Otherwise, you end up with confusing messages like "You must enter a value and it must be between 1 and 10."

Another special rule relates to CompareValidator and RangeValidator when the input fields cannot be converted to the specified data type. The evaluation of validity for the CompareValidator with ControlToCompare specified goes like this:

  1. If the input field referenced by ControlToValidate is blank, it is valid.
  2. If the input field referenced by ControlToValidate cannot be converted to the data type, it is invalid.
  3. If the input field referenced by ControlToCompare cannot be converted to the data type, it is valid.
  4. The inputs fields are converted to the data type and compared.

The third step may seem a little counterintuitive. The step works this way because it would be hard to write a meaningful error message for the validator if it were checking the validity of more than one field at a time. A separate validator should be used to report on error conditions in the ControlToCompare input field. RangeValidator works in a similar way with its maximum and minimum properties.

Effect of Enabled, Visible, and Display Properties

The difference between the Enabled, Visible, and Display properties on validators may not be immediately obvious.

Display=None can be used to specify a validator that does not display anything directly, but still gets evaluated, still affects overall validity, and can still put an error in the summary on both client and server. For client-side validation, these values determine whether the visibility or the display style attributes are used to turn the validator on or off. For server-side validation, Display=Dynamic means that nothing at all displays when the input is valid, while Display=Static means that a single nonbreaking space ("&nbsp") is emitted. This last behavior exists so that table cells containing only validators do not collapse to nothing when valid.

Why not just use Visible=false to have an invisible validator? In ASP.NET the Visible property of a control has a very strong meaning: a control with Visible=false will not be processed at all for pre-rendering or rendering. As a result of this stronger meaning, Visible=false for a validator means that not only does not it not display anything, it is does not function either. It is not evaluated, does not affect page validity, and does not put errors in the summary.

Enabled treads middle ground here. For the most part, Enabled=false has the exact same effect as Visible=false. However, in client-side validation, a disabled validator is still sent to the browser, but in a disabled state. You can activate it with the ValidatorEnable function in client script.

When using Visible or Enabled to control whether validation takes place, bear in mind the sequence of events on the server above. Either change them before validation takes place, or re-validate afterwards. Otherwise, their IsValid values may not reflect the changes to their properties.

The CustomValidator Control

The easiest way to extend the validation framework is to use the CustomValidator control. This can be used either to perform validation that is not covered by something another validation control can do or to perform validation that requires access to information on the server, such as a database or Web service.

If you add a CustomValidator with just a server validation function defined, you will notice that it does not take part in client-side validation. The CustomValidator is not updated as users tab between fields, and it requires a round trip to the server to perform its validation. If you are using a CustomValidator to perform a check that does not need any information that lives on the server, you can also have your validator fully participate in client-side validation by using the ClientValidationFunction property. It is assumed that if you provide a ClientValidationFunction, it should ideally perform exactly the same checks as your server validation handler. Failing that, it should perform a subset of that verification. Don't have a client validation function that does more verifications than are performed on the server, as hackers will be able to bypass it easily.

Here is a simple example of a CustomValidator that works on the client and the server that just checks to see that the input is an even number. First, here is the server function (in C#):

protected void ServerValidate(object source, ServerValidateEventArgs args) {
    try { 
        int i = Int32.Parse(args.Value);
        args.IsValid = ((i % 2) == 0);
    } catch {
        args.IsValid = false;
    }        
}

Here is how it is declared on the client, along with a client validation function that performs the same check. This would usually be in JScript, although it can also be VBScript® if you are targeting Microsoft® Internet Explorer.

<asp:CustomValidator id="CustomValidator1" runat="server" 
    ErrorMessage="Number not divisible by 2!" 
    ControlToValidate="txtCustomData" 
    OnServerValidate="ServerValidate" 
    ClientValidationFunction="CheckEven" /><br>
Input:
<asp:TextBox id="txtCustomData" runat="server" />
<script language="javascript">
<!--
function CheckEven(source, args) {
    var val = parseInt(args.Value, 10);
    if (isNaN(val)) {
        args.IsValid = false;
    }
    else {
        args.IsValid = ((val % 2) == 0);
    }
}
// -->
</script>

Here are some points to note about using CustomValidator:

  • Like all the other validation controls (besides RequiredFieldValidator), it is considered valid if the input field is blank.
  • For older browsers or when client validation is turned off, your client validation function will not get called. You don't have to check the browser capabilities yourself before defining the function, but you do need to make sure it does not cause script errors just by being defined. Always enclose your client code in an HTML comment, as in the example.
  • Two parameters are passed into your client function, corresponding to the parameters that are passed to the server function. The first is the client validator element, and the second is the equivalent of the arguments on the server. It has two properties, the Value, which contains the input to be validated and IsValid, which you can update to indicate validity.
  • You can leave the ControlToValidate blank. In this mode, the server function always fires once per round trip and the client function always fires once for each attempt to submit. You can use this to validate controls that cannot otherwise be validated, such as a CheckBoxList or stand-alone radio buttons. It can also be useful when the condition is based on multiple controls and you don't want it evaluated as the user tabs between fields on the page.
  • You can also hook up the change events of more than one control. You can do this by having some inline script that calls the client function ValidatorHookupControl, described above.

Which Controls Can Be Validated?

In order to be referenced by a validation control, a control must have a validation property. All controls that can be validated have a ValidationPropertyAttribute, which indicates which property should be read for the purposes of validation. If you write your own control, you can make it take part in validation by providing one of these attributes to specify which property to use.

For validation to work client-side as well, this property must correspond to the value attribute of the HTML element that gets rendered on the client. Many complex controls such as DataGrid and Calendar do not have a value on the client and can only be validated on the server. This is why only controls that correspond closely to HTML elements can be involved in validation. Also, a control must have a single logical value on the client. This is why RadioButtonList can be validated, but CheckBoxList cannot.

That's It

This is probably more than you wanted to know about ASP.NET validation. Have fun with it!

© Microsoft Corporation. All rights reserved.