Introduction
It makes no difference what kind of development you are working on, user input validation is always an important and often troublesome issue.
What data needs to be validated? What are the valid conditions? Do we need to report every error in one pass or allow the user to correct one error at a time? Do we validate on the client or on the server? The questions go on.
Worse than that, the code can quickly get unmanageable, especially where we try to validate everything in one pass. Nested ifs, deconstructing strings, building up lists of error messages, endless comments trying to make some sense of it.
It is my personal opinion that everything that can be updated by anyone else but me should be validated at every opportunity and that we should always validate everything in one pass. But that does not mean that I have never tried to dodge the bullet on a tight deadline.
In some respects, it can be worse for the web page developer. It is important to validate as much as possible at the client to save on bandwidth, but HTTP requests are so easy to fake; the same data needs to be validated again on the server, if you want any kind of security.
Another problem can arise if the customer wants you to mark the invalid fields and then list the errors in one place (a common user-friendly convention in web-page design).
It is hard to imagine someone who enjoys writing validation code, and that goes double for classic ASP developers. Thankfully, the .NET framework has gone a long way to alleviate the problem for the ASP.NET developer with the introduction of six all-encompassing and developer-friendly validation controls:
RequiredFieldValidator |
Ensure that a mandatory field is populated. |
CompareValidator |
Compare one control with another or with a literal. |
RangeValidator |
Check that data is within a given range. |
RegularExpressionValidator |
Use where data must match a given Regex format. |
CustomValidator |
Use a custom validation function to validate a control. |
ValidationSummary |
Collate the results of an entire form's validation. |
This article assumes a basic knowledge of adding controls to a web form and setting the properties and event handlers. It attempts to give a general overview of these controls and how to use them to perform simple practical tasks.
All examples use C# with GridLayout
pages. But anything in this article should easily translate to VB.NET and / or FlowLayout
pages.
BaseValidator controls
All of the above, with the single exception of ValidationSummary
, are derived from the BaseValidator
class and share some functionality.
The first four validation controls are tailored for specific purposes and designed to validate an identical condition on both the client and the server. Each generates some purpose-specific JavaScript (using WebUIValidation.js, in the aspnet_client web) to validate the input control that you point to against the condition that the given validation control is designed to handle.
The CustomValidator
allows you to define your own validation routines, using an event handler on the client, server or both.
The actual client-side validation takes place in events triggered by a change of value in the control you are validating and when you try to submit the form. The server-side validation takes place when you post back the form and the validation results can be accessed using the Form
class's .IsValid
property.
Each control's properties look reasonably similar. Apart from the standard font and formatting properties, each of the standard controls has the following common properties, derived from BaseValidator
:
.ControlToValidate |
Name of the control we are validating. |
.ErrorMessage |
The error message related to an invalid condition. |
.Text |
Text to display in this control if invalid. |
.EnableClientScript |
Validate client-side and server-side. |
.IsValid |
The validation result of this control |
ControlToValidate
A string
containing the name of the Control
object that the validation control will examine. This control must be developed using the ValidationPropertyAttribute
.
Of the standard controls, the only ones marked with a ValidationProperty
are:
TextBox
ListBox
DropDownList
RadioButtonList
HtmlInputText
HtmlInputFile
HtmlSelect
HtmlTextArea
It is worth bearing this in mind if you are ever developing a control that may require validation. You should always prefix the class definition with [ValidationProperty("MyProperty")]
(where MyProperty
is the name of the property that can be changed by the user input, usually text).
ErrorMessage
This defines the message related to the invalid condition. This can be displayed in the control itself or passed to a ValidationSummary
control.
Text
The text to be displayed in the control when the condition is not valid. If left blank, the control will display the ErrorMessage
text.
If the difference between .ErrorMessage
and .Text
is confusing, read on. It should make sense by the time we examine the ValidationSummary
control. Until then, we will only use the .ErrorMessage
property (.Text
should be left blank)
EnableClientScript
By default this is set to true
and the .ControlToValidate
will be validated twice, at the client and at the server.
If the .EnableClientScript
property is set to false
, then validation will be limited to the server.
IsValid
The only property not available to the WebForm designer, .IsValid
contains the validation result for this control after the form's validation has taken place.
This can be useful if you want to react differently when only part of the page is invalid.
Examples
The following sections will refer to a very simple example. As shown in the screenshot, this example will consist of a single TextBox
being validated. You know the kind of thing: Leave your E-mail address here and we'll send you something really nice for free. (and then sell your email address to five hundred Email spammers)
Create a web form, containing only a Label
("Email Address:"), an empty TextBox
, a submit Button
and whichever -Validator control we are looking at. (see sample image)
The ControlToValidate
property should always point to the TextBox
and the ErrorMessage
should contain something relevant to the validation we are working with.
Include some code in a Button_Click
handler to redirect to another page when the button is clicked, but only if the page is valid. For example:
private void myButton_Click(object sender, System.EventArgs e)
{
if (this.IsValid)
Response.Redirect("anvokay.aspx", true);
}
Or... if you are feeling lazy, you can download the sample code at the top of the page and unzip it into a new or existing web.
NOTE: There is a strange behavior quirk in the interaction between web browsers and the .NET framework which means that if you hit Enter in a Single-TextBox
form it will post back to the server but the Button_Click
handler is not executed.
This is apparently a legacy browser issue where, for a single textbox form, the button name is not sent as part of the response, leaving .NET with no idea what triggered the postback.
Possible workarounds are:
- Always physically click the Submit button rather than pressing Enter.
- Include a second
TextBox
that does nothing, just to avoid this "feature".
- Download and use MetaBuilders' Default Buttons Control to safely link the Submit Button to the
TextBox
's enter key.
(Thanks to Andy Smith for helping me understand this)
The RequiredFieldValidator
As the name suggests, the RequiredFieldValidator
is normally used to validate that a mandatory field is populated. It can also be customized so that blank is a valid response and some other text means that nothing has been entered.
There is only one property unique to the RequiredFieldValidator
:
.InitialValue |
Consider this text to be "no change" and blank to be "updated". |
Example
Validate our simple form, using a RequiredFieldValidator
, as seen in the example diagram above. If you hit the Submit button without entering any text, you can see that the form does not post back to the server, the JavaScript on the client-side catches the error and displays the error message that you gave it.
If you set .EnableClientScript
to false
and run the application again, then you will see that the submit button now forces a postback but the field is still validated, so if a malicious user attempts to forge the HTTP request to bypass your validation, it will not be successful.
And there you have it. You have now successfully implemented ASP.NET validation with one control and one line of code (if (this.IsValid)
). How easy can it be?
InitialValue
The .InitialValue
property gives you an opportunity to enter some text into the TextBox
and still ensure that it is edited before submission.
Try changing both the RequiredFieldValidator.InitialValue
and TextBox.Text
to "EMAIL ADDRESS" and running the application again.
You should find that if you do not change this text and simply hit "Submit", you will get the error message as specified, but now if you blank the field then it is considered valid.
In many ways, this is a functionality overlap with the CompareValidator
, as will become clear later on. But in some cases it is more sensible (i.e.. more readable) to use the RequiredFieldValidator
- for example, validating that a DropDownList
has changed from the "Please Select One..." default.
BaseCompareValidator controls
The CompareValidator
and RangeValidator
both compare values to literals or other user-entered data. The two controls inevitably contain some common functionality and thus are derived from the BaseCompareValidator
class.
The BaseCompareValidator
only offers us one property in addition to those offered by the BaseValidator
class:
.Type |
Allows strings of specific formats to be compared accurately. |
This one property, however, is very important and includes several "gotchas" that you might need to be aware of.
Type
The .Type
property can be used to define how a string is treated. If a textbox is used for numeric, date or currency values, you can use one of the BaseCompareValidator
controls to validate the type of data entered or compare it to a value of the same type more accurately.
e.g.. If compared directly as strings, "90" is greater than "100"; but if compared as numbers, "90" is clearly less than "100".
But be aware that both the Client and the Server validation will deal with the input data using the locale of the Server machine, unless you set the page or application Culture
/UICulture
properties (see globalization in MSDN).
If you do not set the Culture
and, like me, you develop primarily on an English locale machine (date format: DD/MM/YYYY) and then run your sites live on a US locale server (date format: MM/DD/YYYY) then, as you can see from the screenshot above, the data will be validated differently in development from live.
The 10th of January is before the 1st of October, after all, whichever country you come from.
It is always worth noting on your page which format the user is expected to use (remembering that YYYY/MM/DD is considered culture-independent), but the more idiot-proof you make things, the bigger idiot the internet produces. You cannot guarantee that the user will pay attention to instructions and even if you ask for YYYY/MM/DD, you will still get someone trying to use MM/DD/YYYY without even suspecting that your machine has an English Locale.
It is strongly recommended that you always (even if you develop on the live machine, or one very similar) specify a Culture
in the Web.Config file, for example:
<configuration>
<system.web>
-->
<globalization
requestEncoding="utf-8"
responseEncoding="utf-8"
culture="en-US"
uiCulture="en-US" />
</system.web>
</configuration>
Once you have done this, you know exactly where you stand. You can request the date in format MM/DD/YYYY format and if the user chooses to ignore that and use a culture-independent style then there is no harm done.
If you do not specify a culture, even in the @Page
clause, then you need to be especially careful when comparing to a literal, which may be treated differently in development from production (in fact may cause an exception, if the locale is different and the value becomes invalid). You must then use a culture-independent style for literal dates.
All of this applies equally to numerics in countries which take a comma (,) as a decimal point and to systems using different currency symbols.
Another thing to watch out for is that if any of the values being compared are not of the type the validation is expecting (and in the case of dates, this includes DD-MMM-YYYY, eg. 10-Jan-2003), the form data will be considered valid. The reason for this is that an extra CompareValidator
(using the DataTypeCheck
operator described below) gives you a way out of this and you would generally expect the error message to be different.
The CompareValidator
The CompareValidator
is, at first glance, the least useful of the standard validation controls. However it does have its place and has plenty of designer properties (including the BaseCompareValidator.Type
property, see above) to tweak its functionality in interesting ways.
.ControlToCompare |
Compare the value in this control to the value of another control. |
.Operator |
In what way should we compare the values? |
.ValueToCompare |
Compare the value with a literal value. |
Example
In the simple example, we can use this control to ensure that no one uses a specific E-mail address in the form (I mean, we don't want someone registering us for our own spam E-mail scam, do we?).
Drop a CompareValidator
in place of the RequiredFieldValidator
; set the .Operator
to NotEqual
(why Equal
would ever be the default is beyond me!) and the .ValueToCompare
to your own E-mail address. Do not forget, of course, to set the .ControlToValidate
and .ErrorMessage
properties, as before.
When you run the application and try to enter your own email address into the form, the validator should give you a message as soon as you leave the TextBox
. It should also refuse to let you submit the form to the server.
As pointed out before, this is not entirely useful in itself; for the rare occasions you would use this, you would not mind hacking the RequiredFieldValidator
to do your bidding.
Only when you start considering the combinations of the unique properties listed above do you realize how powerful this is.
ControlToCompare
Instead of comparing a control with a literal value, you can compare it to another user-entered string. You do this by pointing the .ControlToCompare
property to the control you wish to validate.
If this and .ValueToCompare
are both set, .ValueToCompare
will be ignored.
Operator
It is not only possible to compare values for equality (or inequality), it is possible to look for GreaterThan
, GreatThanEqual
, LessThan
or LessThanEqual
.
In all cases, if the condition is true then the .ControlToValidate
is considered valid.
Alternatively, you can even check that the input data can be converted to a certain type, using the DataTypeCheck
value in this property. This is essential, as pointed out above, because a comparison including an invalid value will always be treated as valid.
For example, when comparing two dates, if "F" is compared to "01/01/2003" then it is simultaneously considered to be greater than, less than and equal to. But when checking "F" using a CompareValidator
where .Operator = DataTypeCheck
and .Type = Date
, validation will fail.
When using .Operator = DataTypeCheck
, the .ValueToCompare
and .ControlToCompare
properties are both ignored and the .Type
property is used to define the valid type.
The RangeValidator
The RangeValidator
is much like the CompareValidator
control (hence the common base class), except that it verifies that the user data falls between two constant values, rather than comparing it to a single value (constant or variable).
With that in mind, the control's unique properties (not part of BaseCompareValidator
) are much as you might expect.
.MaximumValue |
Value may not be greater than this. |
.MinimumValue |
Value may not be less than this. |
Example
Referring to our simple form, we can easily use a RangeValidator
to verify that the email address entered begins with a lower-case alpha character. Of course, this is not much validation for an email address, but it is a good demonstration of the RangeValidator
.
Replace whichever validator you are using with a RangeValidator
, set the .ControlToValidate
and .ErrorMessage
as for any validator. Then set .MinimumValue
to a
and .MaximumValue
to z
.
Now any string entered that begins with a lower-case alpha character will be considered valid. Note again that if the TextBox
is left blank, it is still considered valid. A separate RequiredFieldValidator
must be used if the field is mandatory.
MaximumValue
The value entered by the user may not be greater than this, but the two values can be equal.
MinimumValue
The value entered by the user may not be less than this value (unless blank), but the two values can be equal.
The RegularExpressionValidator
Arguably the biggest, most versatile weapon in the ASP.NET validation arsenal has to be the RegularExpressionValidator
.
With just one custom property over those provided by the BaseValidator
class, a world of options become available.
.ValidationExpression |
Value must be in the format described. |
Example
In our simple example, replace the current validator with a RegularExpressionValidator
and set the .ControlToValidate
and .ErrorMessage
as usual.
Set the .ValidationExpression
to \w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
. If you are using Visual Studio .NET, you can click the ellipses (...) button alongside the property and select Internet Email Address
from the default list.
Whilst not perfect email address validation (see below), this is a pretty good approximation. It ensures that the address consists only of "word" characters (A-Z,a-z,0-9,_) with exactly one @ symbol and at least one period (.) after it, allowing for variations which include hyphenations in mid-word and extra periods before and after the @ symbol.
Again, notice that a blank field is always valid unless you also use a RequiredFieldValidator
. And, as with all validation controls, no code is required to perform an identical validation on both the Client and the Server.
ValidationExpression
The data entered, if not blank, must match the format specified in regular expression format.
This article will not attempt to cover regular expressions in detail, much has been written elsewhere on this site and others on this subject.
Frankly, I do not claim to understand it that well, I usually manage with the default values provided by Visual Studio .NET or others that I find through Google (there are some fascinating expressions out there, if you look hard enough).
The Visual Studio .NET default values can be found via the ellipses (...) button to the right of the property (within the property sheet) and offer the following validations:
US / French / German / Japanese / PRC Phone Number
US / French / German / Japanese / PRC French Postal Code
US / PRC Phone Number
Internet Email Address
Internet URL
There are some surprising omissions here (certain nationalities, US State Abbreviations, ISBN, Credit Cards, Long Date Formats), and some of the default expressions (inc. Email address) are not 100% accurate.
The Regular Expression Library is the best resource I have found for most of those that are missing and for improving those that exist.
If anyone has other resources, I would be happy to list them with the next update.
The CustomValidator
Sooner or later (probably later, to be honest), you are going to need to validate something and the standard validators are not going to offer it. Maybe you need to access a database, or perform some complex validation involving numerous controls.
If you are going to need to do it repeatedly, you might want to consider creating your own validation control, but for the one-off oddity you can use the CustomValidator
.
The CustomValidator
allows you to add code to be run on the client and / or the server and set a value showing the result.
You can populate the .ControlToValidate
property, passing the value of the control to the event handler or script. But, unlike the other validation controls, the CustomValidator
will not throw an exception if .ControlToValidate
is empty.
Other than that, it acts exactly the same as any other validation control, either showing the .ErrorMessage
in the control itself or passing it to a ValidationSummary
control.
The CustomValidator
has only one unique property and a unique event handler.
.ClientValidationFunction |
Name of HTML-scripted function to execute, on validation |
.ServerValidate |
Event handler for server-side validation. |
Example
Using our simple example once more, replace the validation control with a CustomValidator
. Set the .ControlToValidate
and .ErrorMessage
properties as before. Ignore the .ClientValidationFunction
property for now and add an event handler for the .ServerValidate
event.
You can validate the value entered against a database, if you wish. For simplicity, I have chosen to simulate this with a simple if
statement.
private void alreadyInUse_ServerValidate(object source,
System.Web.UI.WebControls.ServerValidateEventArgs args)
{
// Default Value
args.IsValid = true;
// Simulating a check against a database
if (args.Value == "pdriley@santt.com")
args.IsValid = false;
}
The validator will always post back to the server to the ClickEvent
handler which the requests the results of the validation. But if you enter one of the "existing" E-mail addresses (in my case, my own address) then the error message will be reported back and the Response
will not be redirected.
NOTE: The ServerValidate
event itself, in fact all server-side validation, is executed after the Page_Load
event and before any other events. But the result of the validation is irrelevant unless you later check that the form is valid. The Button_Click
event, in this case, is still executed.
ServerValidate (Event)
As you can see in the example above, the ServerValidate
event handler should accept an object (the CustomValidator
) and arguments of type ServerValidateEventArgs
.
The ServerValidateEventArgs
class exposes two properties: .IsValid
and .Value
. All you have to do in most cases is check that .Value
is valid and set .IsValid
accordingly.
If there is no .ControlToValidate
property set for the CustomControl
then args.Value
will default to String.Empty
, but you can still validate the values contained in the controls.
ClientValidationScript
Writing a JavaScript handler is conveniently similar to writing the server-side handler. It still exposes the same two arguments, source
and args
.
The sample C# code above would directly translate as:
function alreadyInUse_Validate (source, args)
{
args.IsValid = true;
if (args.Value == "pdriley@santt.com")
args.IsValid = false;
}
All that is left to be done is set the .ClientValidationScript
property to alreadyInUse_Validate
and the client-side validation is complete.
The ValidationSummary
It should be fairly clear by now that many web pages are going to include a lot of validation controls. No user is going to be happy with all those error messages popping up at random positions around the page, but it is always nice to have something marking the invalid fields.
And this is where the ValidationSummary
control comes into play.
The ValidationSummary
derives directly from WebControl
and is not related to the other controls except by symbiotic functionality.
When a page is validated, all of the .ErrorMessage
properties from validation controls in the same container (e.g.. Form
or DataGrid
row) that fail validation are passed to the ValidationSummary
control to be displayed in an unordered list.
This finally explains the .Text
property, common across the validation controls. If .Text
is populated then it will be displayed in place of the validation control, but still .ErrorMessage
will be used to populate the ValidationSummary
.
The ValidationSummary
control has a number of unique properties that affect the way messages are displayed to the user.
.DisplayMode |
How the unordered list is rendered. |
.EnableClientScript |
Should the error list be updated during client validation? |
.HeaderText |
Defines the text that heads the error list. |
.ShowMessageBox |
Show error message list in a javascript "alert". |
.ShowSummary |
Render the error messages on the page itself. |
Example
To demonstrate the full power of the ValidationSummary
, it is necessary to come up with something a little more complicated than the simple example that is used throughout the rest of the article.
Consider a simple registration form for, say, a chat room.
The customer requesting this form has asked for the following:
- User ID - mandatory, must be 6-8 characters
- Password - mandatory, must be 8 characters with at least two numeric, also confirmed
- Name - mandatory
- E-mail - mandatory, must be valid format
- Sex - mandatory, drop down list, must not default
- Date of birth - not mandatory but must be valid if entered
Set the form up yourself, complete with validators, using the rest of this article as a guide. As well as giving each control an error message, change the .Text
properties to *
and drop the new (much smaller) control next to the control it is validating.
Although the Visual Studio .NET IDE will not allow you to drop validation controls directly on top of each other, you can edit the HTML view to force the controls into the same position.
Along with the diagram, here is a clue: there are 12 validation controls in total:
- 6 x
RequiredFieldValidator
- 4 x
RegularExpressionValidator
- 2 x
CompareValidator
Drop a ValidationSummary
at the bottom of the page, next to a submit button. The only property you might want to set is the .HeaderText
. (In the example, it is set to Please fix the following errors:
)
Run the form and you can now see how a combination of validation controls and a ValidationSummary
control can be used to create a very secure and yet flexible and most importantly usable Web Form.
If you get stuck then, again, the example is included in the downloadable zip file at the top of the article.
DisplayMode
The list can be rendered in any of the following DisplayMode
styles:
BulletList
(list items in an unordered list)
List
(delimited only by a new line)
SingleParagraph
(delimited by space)
The default option is BulletList
.
EnableClientScript
By default, .EnableClientScript
is true
and the list of errors will be shown when the form is submitted.
If .EnableClientScript
is set to false
then the ValidationSummary
will not be shown until the server reports the errors back to the client machine.
This is useful if (and only if) the .EnableClientScript
property of all the validation controls are also false
. If they are not then the form will not post back to the server and the ValidationSummary
will never report the errors.
HeaderText
Allows the definition of text to describe the list of errors. This will be posted directly before the list of error messages, regardless of the .DisplayStyle
value.
ShowMessageBox
If set to true
, the ShowMessageBox
property creates a JavaScript, alert()
call to display all of the error messages in a message box.
The message box will emulate the style defined in .DisplayMode
as closely as possible.
ShowSummary
If set to true
, the ShowSummary
property renders the list of error messages into the HTML at the position of the Label
.
Either ShowMessageBox
or ShowSummary
should always be true
, but they should only both be true if absolutely necessary.
Bypassing validation
If you have two postback events (e.g.. "Next" and "Back" buttons), and one requires validation while the other does not, you can set the .CausesValidation
property of one control to true
and the other to false
.
This will bypass both client and server validation. You can still check this.IsValid
, if you want to use some common code for handling both events, and validation will succeed even if there is no valid data entered.
Conclusion
This is a very powerful set of controls with very few obvious dangers. As an added bonus, they are incredibly easy to implement. In Visual Studio .NET, it is just drag-drop and select a couple of properties.
There is a lot of information to absorb in one pass, but hopefully this article will serve as a reasonably simple reference guide as well as a "How to" guide.
Once you have used the validation controls a few times, you will be dropping them into pages just for fun.
Downloads
Simply drop the files into any web application, include the .aspx files in the project and set anvindex.aspx as the start page.