Click here to Skip to main content
Click here to Skip to main content

RequiredIfValidator - Extending from the BaseValidator class

By , 12 Jan 2005
 

Screenshot of Validation

Introduction

When writing ASP.NET pages that need to be validated before submission, I find myself coming across controls that only need to be validated if another control contains a particular value. A classic example of this is an "If other, please specify" field. Typically, you'll have a combobox that contains a list of choices, and at the end of the list is an "Other" item. If the user chooses "Other", you'd like them to specify exactly what "Other" means.

My previous approach was to include a JavaScript method that would make sure the "please specify" field was not empty if the combobox had the "Other" item selected. I would wire up the method to the Form's onSubmit event. This way worked, but each time I did it, I had to manually hard-code the JavaScript method onto the page, changing it a little bit each time to account for control names. Not only was it inefficient, but also error-prone.

To fix the problem, I've created a new validation control that inherits directly from BaseValidator. The control takes my previous approach one step further to make this kind of validation extremely simple.

Background

I'm going to assume some familiarity with the ASP.NET validation controls since this isn't a tutorial on what they are or how they work, but rather an implementation of a custom one. With that said, let's get started.

Introducing the Control

My validator adds a couple of properties to the BaseValidator class. They are:

  • ControlToCompare
  • InitialValue
  • TriggerValue

The ControlToCompare works exactly like the CompareValidator.ControlToCompare property, while the InitialValue property works exactly like the RequiredFieldValidator.InitialValue property. This makes perfect sense since I'm doing a little bit of both types of validation with this control.

The TriggerValue is the only property that's entirely new. When validating, the value of this property will be compared to the value of the control specified by the ControlToCompare property. If the two are equal, it means that the control specified by the BaseValidator.ControlToValidate property is now required. If the values are different, then the BaseValidator.ControlToValidate control is not required.

This may be a little confusing all at once, but hopefully I'll clear it up by the end of the article.

Important Methods

For custom validators, the most important method that you'll be overriding is the EvaluateIsValid method. This method is called for server-side validation of the control. You return true if validation succeeds or false if it doesn't.

For the RequiredIfValidator's EvaluateIsValid, I need to do two things at most. First, I need to see if the control we're validating is required or not. I do this by calling a private method called CheckCompareCondition. The method looks like this:

private bool CheckCompareCondition()
{
    bool isRequired = false;

    string compareValue = this.GetControlValidationValue(ControlToCompare);

    if (compareValue == TriggerValue)
    {
        isRequired = true;
    }

    return isRequired;
}

As you can see, I'm grabbing the validation value of the ControlToCompare control and comparing it to the TriggerValue. If there is a match, then I know that the ControlToValidate is now required. If that's the case, I then have to see if the value of the ControlToValidate is different from the InitialValue. My implementation to do this is exactly what the RequiredFieldValidator does for its EvaluateIsValid method:

    string controlValue;
    controlValue = this.GetControlValidationValue(this.ControlToValidate);

    if (controlValue == null)
    {
        return true;
    }
    else
    {
        return controlValue.Trim() != this.InitialValue.Trim();
    }

Client-side validation is handled by injecting some JavaScript into the page by overriding the OnPreRender method. Basically, I inject the JavaScript by calling the Page's RegisterClientScriptBlock method if the script has not been registered yet. This is the JavaScript method that I inject:

function RequiredIfValidatorEvaluateIsValid(val)
{
    if (val.controltocompare != "") 
    {
        if (document.getElementById(val.controltocompare).value 
                                            == val.triggervalue) 
        {
            //use asp.net's method!
            return RequiredFieldValidatorEvaluateIsValid(val); 
        } 
        else 
        {
            return true;
        }
    } 
    else 
    {
        return true;
    }
}

Here, I'm doing basically the same thing as my server-side EvaluateIsValid method does. The only thing interesting is that I'm actually utilizing ASP.NET's WebUIValidation.js script to do the validation if the ControlToValidate ends up being required.

When looking at the JavaScript method, you may be wondering how I'm able to use val.triggervalue or val.controltocompare. This is where the final important method, AddAttributesToRender comes into play. This method is a virtual method inherited way back from the WebControl class. This method's job is to render any HTML attributes out to the opening tag that are necessary.

For my implementation, I do the following:

    base.AddAttributesToRender(writer);

    if (this.RenderUplevel) 
    {
        //this attribute is needed by the RequiredFieldValidator's
        //validation method.
        writer.AddAttribute("initialvalue", InitialValue);

        //this is the method that asp.net calls when validating my
        //control on the client-side
        writer.AddAttribute("evaluationfunction", 
                             "RequiredIfValidatorEvaluateIsValid");

        //attributes that my client-side method will use to validate itself.
        writer.AddAttribute("controltocompare", 
               this.GetControlRenderID(ControlToCompare));
        writer.AddAttribute("triggervalue", TriggerValue);
    }

Here, you can see I'm adding the attributes that my JavaScript method uses when validating on the client-side. The really important one is the evaluationfunction attribute. This method is called by the ValidatorValidate method located in Microsoft's WebUIValidation.js script. This is how my JavaScript method actually gets wired up by ASP.NET to be called when the page validates on the client.

One other thing worth mentioning is that this method is the reason why I didn't inherit my class from RequiredFieldValidator. Specifically, the call to base.AddAttributesToRender is what did it. The RequiredFieldValidator.AddAttributesToRender method adds the initialvalue and evaluationfunction attributes to the tag, and I wanted to add my own evaluationfunction attribute to the tag. The problem is that the attribute has now already been written, so I'd be adding it a second time with a different value. Both would end up being rendered to the page.

I hear you saying, "Well, then just don't call the base class AddAttributesToRender method!". Well, remember when I said that the method is inherited all the way back from WebControl? The WebControl class does quite a bit of important work in this method, and so does the BaseValidator. There's no reason I should painstakingly re-write all of that functionality in my own control, which could be error-prone.

Conclusion

Hopefully, this article has given you a closer look into the internals of a validation control (maybe closer than you wanted!), but most importantly, I hoped you've learned something new after reading this. At the very least, I hope you'll be able to put this control to good use.

Using the code

The source code is only one file, RequiredIfValidator.cs. To build the control, you can run the following from a command line:

    csc /t:library RequiredIfValidator.cs

Once compiled, you can throw the DLL in your web application's bin directory. Then, when you want to use the control in one of your aspx pages, just include the following directive in your page:

<%@ Register TagPrefix="BNewtz" Namespace="BNewtz.Controls" 
                      Assembly="RequiredIfValidator" %>

Using the demo

The demo is just a simple webpage that shows the functionality of the control. The demo contains the demo.aspx page as well as the compiled DLL of the control. Just throw the demo.aspx page in a virtual directory, and the DLL into the bin subdirectory of your application. Once that's done, just view demo.aspx in your web browser of choice.

Points of Interest

For those of you curious to know how I was able to know what was going on in Microsoft's own controls, the answer is Anakrino. Anakrino is a decompiler that converts MSIL back into C#. It's a great tool to uncover the inner workings of a class, especially if it's one that you're deriving from.

History

  • 11/16/2004 - Article posted.
  • 1/10/2005 - Bug fix: Thanks to Scott for pointing out that the control did not work correctly when inside of a container control implementing INamingContainer. I've adjusted the code within the AddAttributesToRender method and updated the article and downloads with the new version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Datac0re
United States United States
Member
Brian is a software developer at a manufacturing company in Ohio. He does mostly C# windows and web programming, with a small pinch of VB thrown in when necessary.
 
He is a moderator for the CSharpNET Yahoo group, which you can visit by going here:
 
http://groups.yahoo.com/group/CSharpNET/

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAnyone having trouble with this and JQuery conflicting?memberdschwarz19 Jan '10 - 12:10 
It works fine in FireFox, but in IE 8 even if I have values in both controls I get the validation error.
Generalthe validator only triggered after other validator passedmemberyanhui bian16 May '08 - 6:28 
I make the same change as Glenn Holden did and get it to work for radio buttons. However, the validator only fired after other validators on the page passed.
GeneralRe: the validator only triggered after other validator passedmemberyanhui bian16 May '08 - 11:16 
it worked after I add ClientTarget="downlevel" to the @page directive.
GeneralFirefox client sidememberztron30 Sep '07 - 16:06 
Great control, just one thing...
For those of you trying to get the client side validator to work in firefox, check this out
http://aspadvice.com/blogs/joteke/archive/2006/08/26/Remember-expando-attributes-with-custom-controls.aspx
Replace
writer.AddAttribute("initialvalue", InitialValue);
with
Page.ClientScript.RegisterExpandoAttribute(this.ClientID, "initialvalue", InitialValue);
and similar for the other 3 writer.AddAttribute statements

GeneralVisual Web Developer 2005 ExpressmemberSD0610 Aug '07 - 1:50 
I would like to add your validator into VWD but I do not know how to create a dll from your source file.
 
Could you give a quick run down?
 
Thanks
GeneralRe: Visual Web Developer 2005 ExpressmemberDatac0re11 Aug '07 - 1:43 
I tell you how in the article Smile | :) It's in the "Using the code" section. Just compile the one file from the command line and that will get you the dll. Then add the dll as a reference to your project in Visual Studio.
GeneralRe: Visual Web Developer 2005 ExpressmemberSD0612 Aug '07 - 22:41 
Many thanks. Windows could not find csc.exe. I first had to search for it then cd'd into its folder to run the command. In .NET Framework 2.0 it produced a warning but created the dll anyway:
 
warning CS0618: 'System.Web.UI.Page.IsClientScriptBlockRegistered(string)' is
obsolete: 'The recommended alternative is
ClientScript.IsClientScriptBlockRegistered(string key).
http://go.microsoft.com/fwlink/?linkid=14202'
 
It created the dll no problem in 1.1
 
I have yet to try either.
 
"Everyone was a newbie once"
GeneralRe: Visual Web Developer 2005 ExpressmemberSD0613 Aug '07 - 4:41 
Got the validator installed and working after a little trial and error.
 
Is it possible to set the trigger so that validation fires if anything is input into the compare field e.g. I have two fields, one for an 'invoice reference' and another for an 'invoice value'. I would like invoice value to be required if any text is entered as an invoice reference?
 

GeneralRe: Visual Web Developer 2005 ExpressmemberSD0614 Aug '07 - 6:11 
Can't seem to get it to work as I wished (see previous reply)
 
I am trying to get the validation inside a form Wizard. Instead I used server-side validation on Next or Sidebar navigation. I used a label as a validation error and set the visible property to hide or show the message as required. Works fine. Should any other Visual Web Developer 2005 Express newbee stumble across this post and would be interested in my solution. I'd be happy to post the gist of it here.
 
Kind regards Smile | :)
GeneralValidation of textbox on Checkbox.Checked==true or radio buttons selectionmemberwinsum13 May '06 - 23:43 
Hello all, I'm new to Asp.Net and doing form validations. I got few problems in validation and would be thankful if someone replies:
 
1) I am using AutoPostBack=true and then call my if conditions from CheckedChanged funtion ,for checkboxes and radio buttons selection events to get fired. Is there any other way to make selection without invoking AutoPostBack because it refreshes the form and if there is a password, it removes.
 
2) On checkbox.checked==true, i do enable one textbox so i want to validate that textbox as well if checkbox is checked (textbox shouldn't be empty if checkbox.checked==true). and same with radio buttons.
 
Thanks in advance
GeneralRe: Validation of textbox on Checkbox.Checked==true or radio buttons selectionmemberGlenn Holden21 May '06 - 11:04 
I was able to modify this class slightly and get it to work for check boxes and radio buttons (as the control to compare). The reason it doesn't work as-is is the jscript in the PreRender checks the ControlToCompare's value property. A checkbox doesn't have a value property, instead I used the checked property. Hint: Set the TriggerValue = 1.
 
I made the change in a copy of this class and renamed it to RequiredIfCheckedValidator. Then I changed a few other things so if I use both controls, this and the original, on one page then jscript for one doesn't step on the other.
 
1. In 2 places find "__RequiredIfValidatorMethod" and change to "__RequiredIfCheckedValidatorMethod"
 
2. In 2 places find "RequiredIfValidatorEvaluateIsValid" and change to "RequiredIfCheckedValidatorEvaluateIsValid"
 
And as mentioned above:
3. In 1 place find "(val.controltocompare).value" and change to "(val.controltocompare).checked"
 
4. Change the name of the class from "RequiredIfValidator" to "RequiredIfCheckedValidator"
 
By the way Datac0re, thanks for the code. It saved me lots of time and I learned a few things too! Definately something for the library.
 
GH
GeneralRe: Validation of textbox on Checkbox.Checked==true or radio buttons selectionmembernewguy207 Jul '06 - 3:14 
This method works very well for ie but fails in firefox. Has anyone else had this issue or figured out a way around it?
GeneralRe: Validation of textbox on Checkbox.Checked==true or radio buttons selectionmemberNick Chadwick16 Aug '06 - 5:13 
I am not sure whether this is what causes firefox to fail, but you have not amended the server-side event for a checkbox. The GetControlValidationValue() method returns null if you are passing a checkbox as the parameter. I am running up against this right now, and have not yet managed to find a solution.
NewsMultipleFieldsValidator for ASP.NET 2.0memberAdam Tibi20 Mar '06 - 10:36 
Your article has inspired me to create make this validator Smile | :)
 
http://www.codeproject.com/aspnet/MultipleFieldsValidator.asp
 
Kind Regards,
Adam Tibi
 
Make it simple, as simple as possible, but not simpler.
GeneralRe: MultipleFieldsValidator for ASP.NET 2.0memberDatac0re20 Mar '06 - 13:15 
I'm very flattered Smile | :) Your validator looks like it could be helpful - alas (well, not really Wink | ;) ) since I wrote this article I've become a ColdFusion junkie! C# and ASP.NET still have a warm place in my heart though, just don't use 'em that much at the moment... but anyways, keep up the good work!
 
-- modified at 19:15 Monday 20th March, 2006
GeneralErrormessage Questionmemberdotnetter12326 Sep '05 - 4:44 
Have you been able to write code that dynamically changes the errormessage based on the validation error?
 
For example, if validation on the control failed because a required value was not entered then display "Value is required". Else, if validation on the control failed because the value entered was not in the correct format then display "Value must be entered in the correct format".
 
-- modified at 10:44 Monday 26th September, 2005
GeneralRe: Errormessage QuestionmemberDatac0re26 Sep '05 - 23:54 
Right now that functionality is not included, however you could always extend this control and add the custom behavior yourself since the source code is freely available. It wouldn't be too hard to implement.
QuestionVB?memberjonnyO1 Jul '05 - 0:05 
I do NOT mean to offend but have you ever written this in VB
Thanks
Grennie
AnswerRe: VB?memberDatac0re1 Jul '05 - 10:32 
Nope, I shy away from VB.NET, I don't really like it. As it stands, you can still use it with VB code since it can be compiled into it's own library.
GeneralRe: VB?memberjonnyO1 Jul '05 - 14:34 
Thanks for the response and direction - I appreciate your time!
John
GeneralInheriting from RequiredValidatormemberTerry Aney30 Apr '05 - 10:42 
I wrote a few controls similiar to this... my own RangeValidator that allowed for 'Required' and also 'Control Comparing', so basically the three validators of Range, Required, Compare wrapped into one (so that I only had one validator eating up real estate on my HTML page). Because of this, I wanted to inherit from Range validator so I didn't have to duplicate all the addattributes functionality. This is the function I used to accomplish this and 'get by' your noted problems with inheriting from a .Net validator.
 
Basically I simply do a string replace on the actual 'rendering html' from the control. Maybe this is too expensive or simply just a matter of preference, but I was too lazy to write all the stuff in AddToRender that duplicated what either Range or Compare did, so with this method I was able to inherit from one of them and simply do the other two...maybe it's overkill.
 
protected override void Render( HtmlTextWriter writer )
{
System.IO.StringWriter sw = new System.IO.StringWriter();
HtmlTextWriter htw = new HtmlTextWriter( sw );
 
//Since I inherited from RangeValidator, it created an attrib called evaluationfunction="RangeValidatorEvaluateIsValid",
//but I need it to say evaluationfunction="BTRDateControlIsValid", and I could not 'replace' this text any other way.
//If I used writer.AddAttribute it put two attributes on there. If I tried to do
//this.Attributes.Remove( "evaluationfunction" ) and then this.Attributes[ "evaluationfunction" ]
//in OnPreRender, it also made two attributes. My setting always seemed to be 'first' in order so it 'seemed' to work,
//but the fact that we had two attributes with the same name made me nervous so I did this cludge to make sure I only
//had one.
base.Render( htw );
string render = sw.ToString();

render = render.Replace( "evaluationfunction=\"RangeValidatorEvaluateIsValid\"", "evaluationfunction=\"BTRDateControlIsValid\"" );
writer.Write( render );
}

GeneralClientSide updates: an quick and powerful improvement to your controlmemberDanielHac12 Feb '05 - 6:58 
You can get validation to update on the client side when either control is changed.
Currently only one of your two contols will update the validation.
This means that you don't have to attempt submit before the validation control will update.
 
Also see my acticle on validating multiple controls If you are instrested I also am working on a MultiValidator Class and a AtLeastOneValidator Class to make things much easier.
 
*** In your:
protected override void AddAttributesToRender(HtmlTextWriter writer)
 
*** Right after:
//attributes that my client-side method will use to validate itself.
writer.AddAttribute("controltocompare", this.GetControlRenderID(ControlToCompare));
 
*** Add this:
writer.AddAttribute("controlhookup", this.GetControlRenderID(ControlToCompare));
 

 


GeneralRe: ClientSide updates: an quick and powerful improvement to your controlmemberDatac0re12 Feb '05 - 7:26 
I don't agree that it would be an improvement. The control should be validated on its own, not during the change of another control. Correct me if I'm wrong (it's been awhile since I've looked at it), but IIRC, the CompareValidator, which is similar to my control, doesn't exibit this behavior either. I would like to stick close to the original implementation logic of the base class validators. Plus I think that at its current implementation, the control does its job just fine, so I don't think adding the functionality you described would give any real benefit.
 
Thank you for the suggestion though. Big Grin | :-D
GeneralRe: ClientSide updates: an quick and powerful improvement to your controlmemberDanielHac12 Feb '05 - 7:31 
Adding this would make your control behave more like the compare control.
 
Add a compare control to a page do a submit to get the error to appear. Then if you change either control the error will dissappear.
 
If you do something similar with your control. I don't think it make the error message disappear.
GeneralChild controlmemberRandomName019231232139 Jan '05 - 20:20 
I was most interested in your solution as it happened to be exactly what I was looking for at the time. However the client side script doesn't work if your control is the child of another because the reference is no longer valid.
eg My controltocompare (As in your demo) is set to ddlItems but when I look at my source it has become _ctl0:ddlItems. So the document.getElementById won't find it. Ideally you'd want to pass it the clientid attribute but I'm not sure how to programmatically get this. I guess you could call a find control but it seems expensive...
GeneralSolutionmemberRandomName019231232139 Jan '05 - 20:44 
Well looks like somebody thought of this already as there was a method with my name on it!
Change one line in your method AddAttributesToRender
from
writer.AddAttribute("controltocompare", ControlToCompare)
to (I converted it to VB.net Wink | ;) )
writer.AddAttribute("controltocompare", Me.GetControlRenderID(ControlToCompare))
 
Et voila!
 
Cheers,
Scott.
GeneralRe: SolutionmemberDatac0re10 Jan '05 - 2:41 
Thanks for the heads-up Scott. I just submitted the updated the article, demo, and source files with the fixed code. As soon as they get through the submission process, everything will be updated.
GeneralRe: SolutionmemberRandomName0192312321310 Jan '05 - 8:23 
No worries. Actually I forgot to say thank you for putting up your work for others to use! It is very much appreciated.
 
Thank you.
GeneralLutz Roeder's .Net Reflectormembertafkat27 Nov '04 - 4:14 
This one is a really wonderfull alternative to Anakrino. I suggest to give it a try.
 
-tafkat
GeneralRe: Lutz Roeder's .Net ReflectormemberDatac0re27 Nov '04 - 10:22 
Thanks! I'll check it out.
GeneralAnakrinomembernorm.net17 Nov '04 - 3:21 
Thanks for the tip!

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 12 Jan 2005
Article Copyright 2004 by Datac0re
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid