From the April 2002 issue of MSDN Magazine

MSDN Magazine

ASP.NET

Selectively Enable Form Validation When Using ASP.NET Web Controls

James M. Venglarik, II
This article assumes you're familiar with ASP.NET and Visual Basic .NET
Level of Difficulty     1   2   3 
Download the code for this article: Valid.exe (38KB)
SUMMARY Sometimes the extra controls that come with Visual Studio .NET can be a bit inflexible or they just don't provide enough functionality or flexibility for all situations. The ASP.NET form validation controls, while powerful and easy to use, require that the entire page be valid before it's submitted back to the server. Through the use of the new object-oriented features of Visual Basic .NET, it is possible to extend their functionality to overcome this limitation.
      This article tells you how and helps you decide when it's a good idea to keep validation on the client and when you'd be better off disabling it.
In ASP.NET, you can include validation controls that automatically check user input on a page. While this is not a new function (it was available through JavaScript include files), the implementation certainly is. You no longer have to worry about where to do validation (on the server or client), nor do you have to worry about the client-side code. It's all provided by means of using the five built-in validation controls: Regular Expression, Required Field, Range, Comparison, and Custom.
      Much has already been written about the use of the validation controls, so I won't go into the functionality of each. For those details, see Anthony Moore's articles "User Input Validation in ASP+" and "ASP+ Validation In-Depth". Suffice it to say that it's fairly easy to include the controls on your page; simply specify any parameters that are needed (the control to validate, the regular expression to validate against, and so on), and let it run. That is the beauty of the new controls. You don't have to worry about how they are implemented, just that they are there and functioning.
      However, by including those validation controls on your page, you are implying that you want all of the user's input validated before acting upon it. Even if this isn't the intended effect, you might find out that it is easiest to include these controls to check all the input. In this article, I will explain why you might want to disable those controls and how to do so. I will show you how to disable controls for an entire page, how to disable them from the server, and how to disable them in certain instances on both the client and the server.

The Validation Process

      The following is a simplified description of the process that happens when a validation control is used on a page.
  1. When a page containing validation controls is requested, the user's browser type and version is evaluated to determine if client-side validation routines should be used.
  2. If the browser supports client-side validation, the page loads with the appropriate routines being called from the validation controls. This is handled through a JScript® include file.
  3. When the user moves between controls on a client-side validated form after changing a control's value, the validation events for the control that lost the focus are fired and appropriate error messages (if any) are displayed.
  4. If the user generates a form submit on a client-side validated form, the entire form is evaluated for any validation controls that are not valid. If even one control is not valid, the form will not be submitted.
  5. When the form is posted back to the server through a postback event, it is evaluated for validity on the server. This occurs even if the form was already evaluated on the client.
  6. If any of the validation controls are found to be invalid through the server-side check, the page is redisplayed with the appropriate error messages included.
  7. If the form passes all validation on both the server and client, the page processing sequence continues with the event that triggered the postback firing.
      As you can see, the validation can occur multiple times on the client as well as on the server. This provides enough validation so that if implemented properly, almost no erroneous data will be passed. At times, however, you might want to circumvent this process. Let's see why.

Reasons to Disable Form Validation

      While form validation is a great tool for making sure that invalid or incorrectly formatted data is never acted upon, there are certain situations in which you might want the data to pass through. One instance, as Anthony Moore's ASP+ Validation article suggests, is when you leave the form by means of a Cancel button. Frequently, when entering an order, updating an FAQ, or registering, the user will want to back out of what they are doing. You can provide a link on the page that navigates elsewhere, thereby discarding all of the entered data, but for consistent design every Submit button should have a matching Cancel button.
      Another example is a multipart form. Since you can only have one server-side form (runat=server), you cannot divide the page into multiple forms that will generate postback events. If you have a page that lists FAQs at the top for editing, but at the bottom of the page you provide the means to add a new FAQ, you do not want the errors in editing to prevent someone from adding a new FAQ to the page.
      The trick in these instances is to know when to allow validation and when to prevent it.

Disabling Client-Side Validation on the Entire Form

      An easy way to disable all client-side form validation is to provide the clienttarget=downlevel attribute in the Page directive at the top of your page. The directive looks like this:
<%@ Page Language="VB" codebehind="MyPage1.vb" Inherits MyApp.MyPage1"
                        clienttarget=downlevel> 
You need to be careful when you do this because it not only disables all client-side validation, it also disables any other client-side server control functionality. You can also disable client-side form validation for all validation controls by setting the global page variable Page_PostIfValidationError to True.
      The only time you would do either of these is when you have included some validation controls through a common include for the page or embedded them in a custom control. Alternately, you can opt not to include the validation controls on the page.
      You can disable all client-side form validation using a number of different methods depending upon the type of control that the user activates. For a regular HTML control, you can set the Page_ValidationActive property to false. For example:
<input type=button runat=server id=cmdCancel value="Cancel" onClick="Page_ValidationActive=false;"
                        onServerClick="cmdCancel_Click"> 
Please note that it is important to set the onServerClick event to equal the event you have defined in the codebehind page for the control; otherwise, the event won't fire since you have overridden the onClick event.
      For a server-side control, disabling all form validation requires some client-side code, like this:
<asp:button runat=server id=cmdCancel Text="Cancel" onClick="cmdCancel_Click">
                        <script language="JavaScript> document.all["cmdCancel"].onclick = new function("Page_ValidationActive=false;");
                        </script> 
The effect of these last two examples is that all client-side validation has been turned off when the Cancel button is pressed. You will still have to bypass server-side validation if the server code uses the Page.IsValid property.
      There are a few drawbacks with these examples that I'd like to mention. First, you will receive a JavaScript error on the client when executing the code in the examples in any browser other than Microsoft® Internet Explorer 4.0 or higher. The reason is that the Page_ValidationActive property only exists when the user is using Internet Explorer 4.0 or higher. Also, the examples turn off validation for the entire form, not just certain controls. I will show you how to get around these drawbacks.

Disabling Server Validation

      Another way to disable form validation is through server-side programming. There are a variety of ways to accomplish this task through the control's Enabled, Visible, and Display properties. Each has its pros and cons. You should be careful to note which properties disable validation on the client versus the server and be aware of the effect this might have on overall validation.
      Disabling validation through server-side code gives you the flexibility to turn off validation for certain controls and not the entire page, and also allows this to be accomplished at run time. This is important since you can respond to certain events before you decide to turn off the validation. For example, based on a user's security level, you might want a field to be required only at specific times.

Disabling Client Validation

      As I mentioned, there are drawbacks to disabling all client-side form validation with the methods already discussed. You can circumvent these drawbacks by using code I'll describe in this section.
      If you're like me and want to separate display code from processing code (even to go as far as not embedding JScript in the .aspx file), you can create your own custom control for turning off client-side and server-side validation for certain validation controls. I will go through a simple example of how to disable multiple validation controls from a server-side button control without putting any JScript functions in the .aspx file.
      I built a small application, MyApp, that has one page (MyPage1). The page includes a field for editing the number of copies of a book in inventory (I'll assume the user has chosen this on another page). There is a button to cancel the editing (which acts as a reset button) and one to update the number of copies.

Figure 1 Valid Number
Figure 1Valid Number

      You can also add a new title to the inventory along with the number of copies that are available. Figure 1 shows the page when it is first displayed. Figure 2 shows the page when a user has provided invalid input in the Edit section's Number Available field and moved the focus to another control. Placing the cursor over the error image displays the error and a solution.

Figure 2 Invalid Entry
Figure 2Invalid Entry

      Included in the page are form validation controls for each of the fields. All fields have a Required Field Validation control, while the numeric fields have a Regular Expression Validation control to make sure the input value is numeric. When the user clicks Save Changes, I want the form to be passed back even though there may be errors in the Add section of the form. The same is true when the user clicks Add to Inventory when there may be errors in the Edit section of the form.

Building the Custom Control

      Figure 3 shows all the code for this custom control, which takes the place of a regular Submit button and turns off client-side validation. Let's walk through the code step by step.
      The first detail to notice is the definition of the namespace. When creating a Web Control through the Visual Studio® .NET IDE, this isn't automatically included. I included it in order to reference the control on my Web page.
      Next, I made sure the custom control extended the functionality of a base control. In this instance, I used a button control as the base for the custom control. I extended this functionality with the line:
Inherits System.Web.UI.WebControls.Button 
      The next step was to set up a property to accept a list of form validation controls that I wanted the custom control to disable. I accomplished this by setting up a public property that I called NoFormValList and declared the Get and Set procedures, which store their value in the State object for persistence and easy retrieval.
      One important thing to note about the Get procedure is the use of the statement:
If o Is Nothing Then 
It took me a while to figure out what happened to our old friend Null. Since everything is now an object, Null in Visual Basic® .NET is replaced with Nothing. The only exception to this occurs when returning a value from the database; in this case the value can be of type System.DBNull. So now, instead of using the IsNull function, everything should be tested against Nothing. Also note that when testing against objects, you need to use the Is operator.
      Next, I needed to override the Render method of the button control because I wanted to make it emit extra code if the browser allowed client-side validation. Within this subroutine, the first thing I needed to check, after making sure there were validation controls to disable, was which browser was being used. The following line obtains a reference to the Request object on the page:
Dim theRequest As System.Web.HttpRequest = _ System.Web.HttpContext.Current.Request
                    
Once I had this reference, I checked against it for the browser type and major version. If the browser is Internet Explorer 4.0 or higher, I know that client-side validation will occur. (For more information on the HttpContext object, check out The ASP Column in the July 2001 issue of MSDN® Magazine.)
      Within this conditional, I used the Output object to send text to the HTML page. I began by setting up a JScript function that does the actual disabling of the specified validation controls. I appended the control's ID property to the name of the function so that the function was uniquely identified on the page. If I didn't do this but left multiple instances of this control on the page, all of the functions would be named exactly the same and only the last one declared would be executed.
      Then, using the Split function to put the list into an array, I looped through the array generating the JScript code necessary to disable the validation control. The client-side ValidatorEnable function handled this for me. By specifying false, the function disabled the validation control.
      One important thing to note about the loop itself is the use of the new GetUpperBound method. This method is available in arrays and, unlike the Visual Basic 6.0 UBound function, it actually returns the upper bound of the array. For instance, if I had a zero-based, single-dimension array of 10 items, it would contain elements 0 through 9. The Visual Basic 6.0 UBound function would return 10, but the new GetUpperBound method will return 9. Also note that the GetUpperBound method requires the dimension for the upper bound. This is also zero-based, so the first dimension is referenced as 0.
      After I closed out the function, I added a couple of attributes to the control's output. The first was the onServerClick attribute, which tells the page which method to execute when it returns to the server. I needed to set this, otherwise when I appended the normal onClick attribute the application wouldn't know to execute anything on the server side. I constructed the attribute's value based on the control's ID. This gave me the default name of the event on the server.
      The second attribute I appended was the onClick attribute. This overrides the normal onClick event and points its value to the new JScript function I just defined. When ASP.NET renders the page, it adds an onClick attribute to any Submit buttons for client-side validation purposes. Since I handled the validation manually, I included the code that ASP.NET would have inserted into the extra onClick attribute in the onClick attribute I added. This extra code is what calls the client-side validation routines and is specified as:
if typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); 
Later I will show you how to disable the extra onClick attribute from appearing.
      Now when the user clicks on the button, the client-side onClick function executes. When it gets to the server, the normal server-side ControlName_Click event fires.
      The last two lines of the routine apply to all browsersâ€"not just Internet Explorer 4.0 or higher. The first line prevents ASP.NET from inserting the client-side validation call into an extra onClick attribute. The last line of the routine, MyBase.Render(output), writes out the code for the control. Notice that it is outside of the conditional since I have to render the control no matter what browser is being used.
      Next I needed a way to turn off the server-side validation of the same validation controls. The DisableServerSideValidation method handles this for meâ€"I just needed to call it in the right place.
      The first thing this method does, after checking to make sure there are validation controls to disable, is break up the list of validation controls that you want to disable into an array. The code loops through the array again, but this time for server-side processing. I used the FindControl function to obtain a reference to the current control in the list.
      After validating that the control exists (note the use of the Is operator again), I used a Select Case conditional on its type to determine what to do next. In each of the Case statements, I converted the control into its real type and set its IsValid property to True. Since all of this occurs after all of the validation controls have been validated, this has the effect of overriding the invalidity of any of the validation controls in the list. Therefore, when the Page.IsValid property is called, it will return True (as long as the other validation controls not in the list are valid).
      That's it for the custom control. Let's move on to the page itself and see how to use this new control to turn off the validation.

Using the Custom Control in the Display

      The display portion of the page is in the .aspx file. At the top of the page, just under the Page declaration, I included a reference to the new control's namespace so that I could use the control on the page. I did this by making the following declaration:
<%@ Register TagPrefix="nfvc" Namespace="MyApp.NoFormValControls" Assembly="MyApp"
                        %> 
Note the use of the application name in the namespace attribute. I have seen many instances where the application name is not used, but I have not gotten the registration to work without it. Also note that the Assembly attribute is required. I simply set it to the name of the application.
      Now I can put my control within the server-side form. You can get the full code for the MyPage1.aspx file at the link at the top of this article. Next, I'll look at adding the Save Changes button to the page.
      Let's go ahead and assume that the Add section's Title field has a required field validator with the ID of valReqTitle and that the Add section's Number Available field has a regular expression validator and a required field validator with the IDs of valREAdd and valReqAdd, respectively. I then put the Save Changes button on the form with the following code:
<nfvc:NoFormValButton id="Save" runat=Server text="Save Changes >>"
                        NoFormValList="valReqTitle,valREAdd,valReqAdd"> </nfvc:NoFormValButton>
                    
Using the TagPrefix of nfvc that I defined at the top of the page, I declared the NoFormValButton control, setting the runat, id, and text attributes. I also set the new NoFormValList attribute to a comma-separated list of validation controls I want to turn off when the Save Changes button is clicked. Figure 4 shows the source code when viewed through the browser for Internet Explorer 4.0 or higher. For all other browsers (that don't support the client-side validation), the control looks like the following:
<input type="submit" name="Save" value="Save Changes >>" id="Save"
                        /> 
      I then added the Add to Inventory and Cancel Editing buttons, indicating which validation controls to disable in their respective NoFormValList attributes.

Using the Custom Control on the Server

      The last thing to look at is the .vb codebehind page, which handles all of the processing on the server side. One of the first things to do on this page is include a reference to my new control. I did this with a normal Imports statement at the top of the page:
Imports MyApp.NoFormValControls 
Within the class definition of the page itself, I hooked the page controls into local controls, making sure I declared the buttons with a type of NoFormValButton.
      Be sure to note the requirement of the Handles ControlName.Event directive at the end of each of the event declarations. If you omit this directive, your procedures won't fire. For example, the Click event of the Add button should be declared:
Protected Sub Add_Click(ByVal Sender as object, _ ByVal e as Eventargs) Handles
                        Add.Click 
      Lastly, in each of the Click events for the buttons is a call to the DisableServerSideValidation method for the button. This disables the server-side validation of the list of controls I defined back on the .aspx page. The call for the Save Changes button is simply:
Save.DisableServerSideValidation() 
      After I hooked in the other Click events, I was finished. When I tied this all together, I got a page that allows you to have invalid input in the Add to Inventory part, but still lets you cancel editing and save changes. Also, it allows you to have invalid input in the Edit Inventory part, while leaving Cancel Editing and Add to Inventory working. As an added benefit (or perhaps the best benefit), you now have a custom server control that you can reuse in many other applications.

Conclusion

      Form validation controls are just one of the many new tools available to developers for performing the kinds of tasks that are frequently required of Web sites. If these tools aren't exactly what you need for all your scenarios, you can take advantage of the features of ASP.NET and Visual Basic .NET to override and extend these controls to make them fit your particular needs.
For background information see:
.NET Framework Class Library: BaseValidator Class
ASP.NET: Web Forms Let You Drag And Drop Your Way To Powerful Web Apps
Visual Programmer: Server-side Controls in Active Server Pages+
James M. Venglarik, II is an independent consultant in Raleigh, NC. He specializes in architecting and developing Microsoft-based Web and integration applications for Fortune 500 corporations. You can reach him at psiklops@bellsouth.net.