ASP.NET validation in groups






4.39/5 (13 votes)
Sep 13, 2001
3 min read

223126

1403
How to circumvent the all-or-nothing validation model of ASP.NET
The problem
The validation controls of ASP.NET offer a great deal of flexibility and comfort when you need to write data entry pages in your web application. They provide the ability to ensure valid data in the input fields in a rather declarative than procedural way. So far so good.However, I came across the following problem during the development of a portal application. It uses user controls to customize the modules and the layout that make up the portal page. Now I had two modules with some input fields each which checked the validity with standard validation controls offered by ASP.NET. It all was fine until I configured the portal to display both modules at same time on the page. Every time I tried to enter (valid) data in one of the modules the other one complained about empty required fields. This behaviour is by design as validation occurs on the page-level (the Validators collection is a member of the Page object). However, it was not the intended behaviour as I wanted it.
While thinking about a solution for this, I realized that this is not an uncommon problem. Imagine a form that handles different payment methods. Depending on the selected method different fields should be mandatory while others remain empty.
How validation works
In order to find a solution it's always good to understand how things work. According to MSDN a server round-trip includes the following steps:
- Create page and control objects according to the aspx file
- Recover state of the objects from viewstate
- Update objects based on user input
- Fire Page_Load event
- Fire change notification events
- Save object state in viewstate
- Render HTML
Validation occurs between step 3 and step 4 in the case of a postback. This usually does make sense, as it ensures that the validation takes place before user code is executed as a response of an event. However it makes life a bit harder when controling the validation process itself.
The Solution
The first approach was to look for a built-in standard way of solving this, as it seemed a common problem to me. I didn't find one. After trying various approaches to tackle this, I came up with the solution below. The beauty of this approach is the fact that you don't have to use custom controls and don't have to change existing pages too much. It's all centered around two little single functions. However, it also has one big drawback: you have to disable client side validation for uplevel browsers.
Your aspx page looks like the following:
<%@ Page language="c#" Trace="true" clienttarget="downlevel" Codebehind="default.aspx.cs"
AutoEventWireup="false" Inherits="GroupValidator.TestForm" %>
....some HTML here ....
<form id="Form1" method="post" runat="server">
<table border=1>
<tr><td>
<asp:CheckBox id="Box1" Text="Validate Group 1" runat="server"/>
<asp:Placeholder id="Group1" runat="server">
<asp:TextBox id="Text1" runat="server" wordwrap=false
height="70px" width="100" rows="15"></asp:TextBox>
<asp:RequiredFieldValidator id="RequiredFieldValidator1"
ControlToValidate="Text1"
Display="Static"
InitialValue="" Width="100%" runat="server">*
</asp:RequiredFieldValidator>
<asp:CustomValidator id="CustomValidator1" runat="server"
controltovalidate="Text1"
errormessage="ID is already in use."
OnServerValidate="CheckID" />
</asp:Placeholder>
</td></tr><tr><td>
<asp:CheckBox id="Box2" Text="Validate Group 2" runat="server"/>
<asp:Placeholder id="Group2" runat="server">
ZIP Code: <asp:TextBox id="Text2" runat="server" wordwrap=false
height="70px" width="100" rows="15"></asp:TextBox>
<asp:RequiredFieldValidator id="RequiredFieldValidator2"
ControlToValidate="Text2"
Display="Static"
InitialValue="" Width="100%" runat="server">*
</asp:RequiredFieldValidator>
<asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server"
ControlToValidate="Text2"
ValidationExpression="^\d{5}$"
Display="Static"
Font-Name="verdana"
Font-Size="10pt">
Zip code must be 5 numeric digits
</asp:RegularExpressionValidator>
</asp:Placeholder>
</td></tr>
</table>
<asp:Button id="Button1" text="Validate" OnClick="Button1Click" runat="server"/>
</form>
....some more HTML here ....
So what are we doing here? The first thing to note is the ClientTarget Page directive. It's set on "downlevel" to disable client-side validation scripts. The second thing are the two PlaceHolder tags that contain some input fields as well (and that's the important point) some validation controls. They define the validation groups. The placeholders here are just an example. If you use this technique in an user control you could also use the control itself as a grouping container.
What's next?
With the current aspx file all four validators would trigger on a postback event. Nothing new until now. In fact we don't care about it! Let's look at the Button1Click function:
public void Button1Click(Object Sender, EventArgs evt) { Trace.Write ("Entering Validation"); DisableValidators(); if(Box1.Checked == true) { Trace.Write ("Group 1 Validation"); EnableValidators(Group1); } if(Box2.Checked == true) { Trace.Write ("Group 2 Validation"); EnableValidators(Group2); } Page.Validate(); if(Page.IsValid == true) Trace.Write("The page is valid"); }
What we are doing here, is basically to re-run the validation process. Before we do this, we reset the status of the validation controls according to the selection. To define the group we need a container obejct - our placeholder. The actual work is done by these functions:
private void DisableValidators() { Trace.Write ("Disabling all validator controls on page"); foreach (BaseValidator bv in this.Validators) bv.Enabled = false; } private void EnableValidators(Control container) { foreach (Control c in container.Controls) { if(c is IValidator) { Trace.Write("Enabling " + c.ID); // Assumption here: // every control that implements IValidator is derived from BaseValidator class ((BaseValidator)c).Enabled = true; } } }
These two functions can be placed wherever they fit. So basically we are running the validation twice as we cannot change the default validation. The only way to circumvent this, is to disable the validators by default in the aspx page.