Click here to Skip to main content
15,919,500 members
Articles / Web Development / HTML

Multiple Fields Validator - An ASP.NET Validation Control

Rate me:
Please Sign up or sign in to vote.
4.95/5 (98 votes)
7 Apr 2006Ms-PL4 min read 788.7K   5.5K   134   184
Discussing the MultipleFieldsValidator that validates a group of fields in which at least one is required, like phone number, mobile phone number, or email. It inherits the BaseValidator and uses some new cool ASP.NET 2.0 features.

Sample Image

Introduction

In an ideal world, web users don't need to authenticate because they are trustworthy, no try-catch because there wouldn't be any bug or unexpected behavior, and you wouldn't be using web validators because users will be submitting valid entries. But since we are not living in that world, I hope we would some time, you might find this validator useful, so bear with me.

Developing with ASP.NET, I've encountered many cases when you only need one field to be filled out of many given fields. To validate in such cases, I used to have a custom validator, or a client-script, or sometimes did server-side validation only.

The problem with a custom validator, and all other ASP.NET validators, is that they are mainly designed to handle one or two fields. Also, you need to write custom server-side and client-side code, or maybe copy and paste code, every time you encounter validating multiple fields. To solve this, I have made this light multiple-controls validator, and got use of the new ASP.NET 2.0 features.

Background

Even with ASP.NET 2.0, a lot of valuable validation controls are missing, especially those for multiple-fields validation. Peter Blum has created a set of commercial controls called Professional Validation And More to fill this gap and he, when answering my forum post, gave me the idea of implementing my own validator.

I have also found a good article on CodeProject by Daniel Hacquebord on CustomValidator dependent on multiple controls that addresses a similar need, however, this validation control has the following advantages:

  1. Doesn't require writing any client or server-side code. Just drop it on the page and assign it the controls that you want to validate.
  2. HTML/XHTML compatible. It assigns additional attributes with JavaScript rather than adding them directly to the span tag. That is, it uses JavaScript document["MyMultipleFieldsValidator"].condition = "OR" rather than the HTML <span id="MyMultipleFieldsValidator" condition = "OR">.
  3. Uses a .js resource file to register the client-side code rather than writing the client-side code in every page that requires this control.
  4. Inherits directly from BaseValidator versus CusomValidator, thus, gaining a tiny extra performance.

There is another good article on CodeProject that gave me some inspiration, called RequiredIfValidator - Extending from the BaseValidator class about a validator that validates another field based on the selection of a drop down list.

Using the Code

BaseValidator, the base of this control, is mainly made to handle one control so I had to disable some properties that is meant to validate a single control. I had to shadow the ControlToValidate and SetFocusOnError properties and hide them from the designer and the Visual Studio editor.

C#
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new bool SetFocusOnError {
    get {
        return false;
    }
    set {
        throw new NotSupportedException("SetFocusOnError is not supported.");
    }
}

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new string ControlToValidate {
    get {
        return string.Empty;
    }
    set {
        throw new NotSupportedException("ControlToValidate is not supported.");
    }
}

In the AddAttributesToRender, I am using the ASP.NET 2.0 RegisterExpandoAttribute method to add attributes, using JavaScript, to the <span> tag generated by the validator rather than adding them statically, hence, keeping it HTML/XHTML friendly.

C#
if(this.RenderUplevel) {
    string clientID = this.ClientID;
    Page.ClientScript.RegisterExpandoAttribute(clientID, 
        "evaluationfunction", 
        "MultipleFieldsValidatorEvaluateIsValid");
    Page.ClientScript.RegisterExpandoAttribute(clientID, 
        "controlstovalidate", 
        this.GenerateClientSideControlsToValidate());
    Page.ClientScript.RegisterExpandoAttribute(clientID, 
        "condition",
        PropertyConverter.EnumToString(typeof(Conditions), Condition));
}

In the OnPreRender, I've used the new ASP.NET 2.0 method RegisterClientScriptResource to add my embedded .js file to the page. There is a good article by Gary Dryden on CodeProject called WebResource ASP.NET 2.0 explained that deals with this subject.

C#
protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);
    if (base.RenderUplevel) {
        this.Page.ClientScript.RegisterClientScriptResource(
            typeof(MultipleFieldsValidator),
            "AdamTibi.Web.UI.Validators.WebUIValidationExtended.js");
    }
}

Using the Validator

To use this validator from your Visual Studio IDE, you need to add the provided .dll to the toolbox. For more information on this, check the MSDN documentation.

After dropping the validator on a webform, all you need to do is to set the ControlsToValidate property, from the Properties window, to point to the controls that you want to validate. This summarizes the generated code; however, the demo attached with the article has more examples.

The Condition property, which is set to OR by default, sets the condition that you want to apply when validating multiple fields. So, OR ensures one of the fields is filled, XOR ensures one of the fields is filled but not all of them.

ASP.NET
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Register TagPrefix="atv" Namespace="AdamTibi.Web.UI.Validators" 
    Assembly="AdamTibi.Web.UI.Validators" %>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox ID="txtPhone" runat="server"></asp:TextBox>
<asp:TextBox ID="txtMobile" runat="server"></asp:TextBox>
<asp:TextBox ID="txtEmail" runat="server"></asp:TextBox>
<atv:MultipleFieldsValidator ID="mfv" runat="server" Condition="OR"
    ControlsToValidate="txtPhone,txtMobile,txtEmail">
    fill at least one field</atv:MultipleFieldsValidator>
</form>
</body>
</html>

Limitations

I've only tested the validator on Internet Explorer 6 and Firefox 1.5; however, I am not using any weird JavaScript so it should work on other browsers as well. Please let me know if it worked for you.

I only need the validator to check TextBox fields so I didn't check it with other types of controls; however, in theory, it should work fine. Also, let me know if you are getting any problems.

Conclusion

I hope I made someone’s day. If you like this article, then please remember to vote. If you have any suggestions, bugs, or enhancements, then hit me with it!

History

  • 10th March, 2006 - First version
  • 4th April, 2006 - Now works with Firefox

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Architect
United Kingdom United Kingdom
Passionate about refining software practices, promoting self-motivated teams and orchestrating agile projects.
Lives in London, UK and works as a .NET architect consultant in the City.

Blog AdamTibi.net.

Comments and Discussions

 
GeneralGreat Pieace of code... BUT... Pin
MikeDotBe6-Jun-07 22:16
MikeDotBe6-Jun-07 22:16 
GeneralRe: Great Pieace of code... BUT... Pin
Adam Tibi6-Jun-07 22:26
professionalAdam Tibi6-Jun-07 22:26 
QuestionRe: Great Pieace of code... BUT... Pin
MikeDotBe7-Jun-07 5:08
MikeDotBe7-Jun-07 5:08 
AnswerRe: Great Pieace of code... BUT... Pin
Adam Tibi7-Jun-07 6:29
professionalAdam Tibi7-Jun-07 6:29 
GeneralRe: Great Pieace of code... BUT... Pin
MikeDotBe8-Jun-07 3:03
MikeDotBe8-Jun-07 3:03 
GeneralRe: Great Pieace of code... BUT... Pin
Adam Tibi8-Jun-07 3:49
professionalAdam Tibi8-Jun-07 3:49 
GeneralRe: Great Pieace of code... BUT... Pin
MikeDotBe10-Jun-07 21:40
MikeDotBe10-Jun-07 21:40 
GeneralVB.NET version and JS tweak Pin
TrickUK6-Jun-07 0:59
TrickUK6-Jun-07 0:59 
Hi Adam,

Thought I'd contribute my VB.NET conversion and also a little tweak I made to the JS file. I had to change the loops to iterate the control name array using an integer index, as I was getting a function definition assigned to the controlID variable; this in turn made ValidatorGetValue fail. I can only assume it's a result my web application being AJAX enabled, or something beyond the scope of my manual!

Regards,

Richard.

Imports System.Web
Imports System.Web.UI
Imports System.ComponentModel
Imports System.Security.Permissions

<Assembly: TagPrefix("AdditionalValidators", "addval")>
<Assembly: WebResource("AdditionalValidators.WebUIValidationExtended.js", "application/x-javascript")>

<AspNetHostingPermission(SecurityAction.LinkDemand, Level:=AspNetHostingPermissionLevel.Minimal), _
AspNetHostingPermission(SecurityAction.InheritanceDemand, Level:=AspNetHostingPermissionLevel.Minimal), _
ToolboxData("<{0}:MultiFieldValidator runat=""server""></{0}:MultiFieldValidator>")> _
Public Class MultiFieldValidator
Inherits System.Web.UI.WebControls.BaseValidator

#Region " Overriden Methods "
Protected Overrides Sub AddAttributesToRender(ByVal writer As System.Web.UI.HtmlTextWriter)
MyBase.SetFocusOnError = False
MyBase.AddAttributesToRender(writer)

If Me.RenderUplevel Then
Page.ClientScript.RegisterExpandoAttribute(Me.ClientID, "evaluationfunction", "MultipleFieldsValidatorEvaluateIsValid")
Page.ClientScript.RegisterExpandoAttribute(Me.ClientID, "controlstovalidate", Me.GenerateClientSideControlsToValidate())
Page.ClientScript.RegisterExpandoAttribute(Me.ClientID, "condition", PropertyConverter.EnumToString(GetType(Conditions), Condition))
End If
End Sub

Protected Overrides Function ControlPropertiesValid() As Boolean
If Me.ControlsToValidate.Trim.Length = 0 Then Throw New HttpException(String.Format("The ControlsToValidate property of {0} cannot be blank.", Me.ID))

Dim controlToValidateIDs() As String = GetControlsToValidateIDs()
If controlToValidateIDs.Length < 2 Then Throw New HttpException(String.Format("The ControlsToValidate property of {0} has less than two IDs.", Me.ID))

For Each controlToValidateID As String In controlToValidateIDs
MyBase.CheckControlValidationProperty(controlToValidateID, "ControlsToValidate")
Next
Return True
End Function

Protected Overrides Function EvaluateIsValid() As Boolean
Dim controlToValidateIDs() As String = Me.GetControlsToValidateIDs
Dim controlToValidateValue As String

Select Case Condition
Case Conditions.OR
For Each controlToValidateID As String In controlToValidateIDs
If MyBase.GetControlValidationValue(controlToValidateID).Trim.Length > 0 Then Return True
Next
Return False
Case Conditions.XOR
Dim previousResult, passedFirstElement, currentResult As Boolean

For Each controlToValidateID As String In controlToValidateIDs
controlToValidateValue = MyBase.GetControlValidationValue(controlToValidateID).Trim

If Not passedFirstElement Then
previousResult = (controlToValidateValue <> String.Empty)
passedFirstElement = True
Else
currentResult = (controlToValidateValue.Length > 0)
If previousResult <> currentResult Then Return True
previousResult = currentResult
End If
Next
Return False
Case Conditions.AND
For Each controlToValidateID As String In controlToValidateIDs
controlToValidateValue = MyBase.GetControlValidationValue(controlToValidateID).Trim
If controlToValidateValue.Length = 0 Then Return False
Next
Return True
Case Else
Throw New Exception("End of validation has been reached without a result!")
End Select
End Function

Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
MyBase.OnPreRender(e)
If (MyBase.RenderUplevel) Then Me.Page.ClientScript.RegisterClientScriptResource(GetType(AdditionalValidators.MultiFieldValidator), "AdditionalValidators.WebUIValidationExtended.js")
End Sub
#End Region
#Region " Helper Methods "
Private Function GetControlsToValidateIDs() As String()
Dim controlsToValidate As String = Me.ControlsToValidate.Replace(" ", "")
Dim controlToValidateIDs() As String

Try
controlToValidateIDs = controlsToValidate.Split(",".ToCharArray)

Catch ex As ArgumentOutOfRangeException
Throw New FormatException(String.Format("The ControlsToValidate property of {0} is not well-formatted.", Me.ID), ex)
End Try
Return controlToValidateIDs
End Function

Private Function GenerateClientSideControlsToValidate() As String
Dim controlToValidateIDs() As String = Me.GetControlsToValidateIDs
Dim controlToValidateIDTrimmed As String
Dim controlRenderIDs As String = String.Empty

For Each controlToValidateID As String In controlToValidateIDs
controlToValidateIDTrimmed = controlToValidateID.Trim
If controlToValidateIDTrimmed.Length = 0 Then Throw New FormatException(String.Format("The ControlsToValidate property of {0} is not well-formatted.", Me.ID))
controlRenderIDs &= "," + MyBase.GetControlRenderID(controlToValidateIDTrimmed)
Next
controlRenderIDs = controlRenderIDs.Remove(0, 1)
Return controlRenderIDs
End Function
#End Region
#Region " Properties "
<Browsable(False), _
EditorBrowsable(EditorBrowsableState.Never)> _
Public Overloads Property SetFocusOnError() As Boolean
Get
Return False
End Get
Set(ByVal value As Boolean)
Throw New NotSupportedException("SetFocusOnError is not supported because you have multiple controls to validate")
End Set
End Property

<Browsable(False), _
EditorBrowsable(EditorBrowsableState.Never)> _
Public Overloads Property ControlToValidate() As String
Get
Return String.Empty
End Get
Set(ByVal value As String)
Throw New NotSupportedException("ControlToValidate is not supported because you have multiple controls to validate")
End Set
End Property

<Browsable(True), _
Category("Behavior"), _
Themeable(False), _
DefaultValue(""), _
Description("Comma separated list of control IDs that you want to check")> _
Public Property ControlsToValidate() As String
Get
If ViewState("ControlsToValidate") Is Nothing Then Return String.Empty
Return ViewState("ControlsToValidate").ToString
End Get
Set(ByVal value As String)
ViewState("ControlsToValidate") = value
End Set
End Property

<Browsable(True), _
Themeable(False), _
Category("Behavior"), _
DefaultValue(Conditions.OR), _
Description("The condition used to compare the value of the fields, e.g. 'OR', will return true if at least one field is valid")> _
Public Property Condition() As Conditions
Get
If ViewState("Condition") Is Nothing Then Return Conditions.OR
Return CType(ViewState("Condition"), Conditions)
End Get
Set(ByVal value As Conditions)
ViewState("Condition") = value
End Set
End Property

#End Region
#Region " Enum "
Public Enum Conditions
[OR]
[XOR]
[AND]
End Enum
#End Region

End Class

function MultipleFieldsValidatorEvaluateIsValid(val) {
controltovalidateIDs = val.controlstovalidate.split(',');
switch (val.condition) {
case 'OR':
for(var i=0,len=controltovalidateIDs.length;i<len;++i) {
if (ValidatorTrim(ValidatorGetValue(controltovalidateIDs[i])) != '') {
return true;
}
}
return false;
break;
case 'XOR':
for(var i=0,len=controltovalidateIDs.length;i<len;++i) {
var controlID = controltovalidateIDs[i];
if (i == '0') {
var previousResult = !(ValidatorTrim(ValidatorGetValue(controlID)) == '');
continue;
}
var currentResult = !(ValidatorTrim(ValidatorGetValue(controlID)) == '');
if (currentResult != previousResult) {
return true;
}
previousResult != currentResult;
}
return false;
break;
case 'AND':
for(var i=0,len=controltovalidateIDs.length;i<len;++i) {
if (ValidatorTrim(ValidatorGetValue(controltovalidateIDs[i])) == '') {
return false;
}
}
return true;
break;
}
return false;
}
GeneralRe: VB.NET version and JS tweak Pin
Adam Tibi6-Jun-07 22:34
professionalAdam Tibi6-Jun-07 22:34 
GeneralGreat Work Pin
RakeshKr31-May-07 21:38
RakeshKr31-May-07 21:38 
GeneralRe: Great Work Pin
Adam Tibi3-Jun-07 22:58
professionalAdam Tibi3-Jun-07 22:58 
GeneralBrilliant: Pin
purlpehippo197817-May-07 0:29
purlpehippo197817-May-07 0:29 
GeneralRe: Brilliant: Pin
Adam Tibi17-May-07 1:08
professionalAdam Tibi17-May-07 1:08 
GeneralThanks Pin
nyousfi22-Mar-07 2:46
nyousfi22-Mar-07 2:46 
GeneralGood Work Pin
GaryWoodfine 9-Feb-07 1:30
professionalGaryWoodfine 9-Feb-07 1:30 
GeneralRe: Good Work Pin
Adam Tibi9-Feb-07 5:10
professionalAdam Tibi9-Feb-07 5:10 
GeneralValidation on other than text boxes Pin
Member 372415814-Jan-07 15:52
Member 372415814-Jan-07 15:52 
GeneralDynamic client-side revalidation Pin
andyqp10-Jan-07 22:29
andyqp10-Jan-07 22:29 
GeneralRe: Dynamic client-side revalidation Pin
Adam Tibi11-Jan-07 22:34
professionalAdam Tibi11-Jan-07 22:34 
Question1.1 Code Pin
Daiya Piyush24-Nov-06 15:22
Daiya Piyush24-Nov-06 15:22 
AnswerRe: 1.1 Code Pin
Adam Tibi6-Dec-06 6:23
professionalAdam Tibi6-Dec-06 6:23 
QuestionControl name with and condition Pin
shalabh_bindlish14-Nov-06 20:34
shalabh_bindlish14-Nov-06 20:34 
AnswerRe: Control name with and condition Pin
Adam Tibi15-Nov-06 4:55
professionalAdam Tibi15-Nov-06 4:55 
GeneralRe: Control name with and condition Pin
shalabh_bindlish15-Nov-06 20:23
shalabh_bindlish15-Nov-06 20:23 
GeneralRe: Control name with and condition Pin
Adam Tibi16-Nov-06 0:25
professionalAdam Tibi16-Nov-06 0:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.