views:

57

answers:

4

Hi all,

I want to extend the asp.net validators such that I can make one validator dependent on another. The situation I have is that we have to validate a date in a textbox. Normally I would just use a combination of a RequiredFieldValidator (to ensure the date is provided), CompareValidator (to ensure the date is a date) and finally a RangeValidator (to ensure the date is within the required limit).

The problem with this is that the validators do not depend on each other, so as a result the user would see possibly all three messages at once for each validator when really all we want them to see is the most relevant message, i.e. if they entered "abc" in the date text box it would not be appropriate to show them the message saying the date was not in the valid range (even though technically I suppose this is true).

Currently to provide this kind of functionality we use a CustomValidator and just put all three validations within the server validate event handler and change the error message programmatically depending on what validation failed.

I would like to standardize this a bit more as it happens quite a bit in this application, I figure if I can make the validators dependent on each other this will solve the problem and also allow us to make use of the client side validation rather than having to do a postback especially to handle the custom validation.

The idea is that if one validator is dependent on another if that "master" is valid then the depended will perform its normal validation (EvaluateIsValid()) otherwise if the master validator is not valid then the other dependent validators will be valid.

I have come up with the following solution by inheriting from the various validator controls that already have been provided in the framework.

public class RequiredFieldDependentValidator : RequiredFieldValidator
{
    [Description("The validation control to depend on for determining if validation should occur")]
    public string ValidationControlToDependOn
    {
        get
        {
            object obj = ViewState["ValidationControlToDependOn"];
            if (obj != null) return (string) obj;
            return null;
        }
        set
        {
            Control control = FindControl(value);
            if (control is IValidator)
                ViewState["ValidationControlToDependOn"] = value;
            else
                throw new HttpException("ValidationControlToDependOn is not a validation control");
        }
    }

    protected override bool EvaluateIsValid()
    {
        IValidator validationControlToDependOn = FindControl(ValidationControlToDependOn) as IValidator;

        if(validationControlToDependOn != null)
        {
            return !validationControlToDependOn.IsValid || base.EvaluateIsValid();
        }

        return base.EvaluateIsValid();
    }

Currently I have just coded it for the RequiredFieldValidator, ideally I would like to provide this functionality for all of the validators but I cannot see a way to do this without copying the above code into a similar class for each individual type of validator I want to provide this functionality for thus if there are any problems I'm going to have to go back and change this code on each validator type individually.

Is there a way I can "centralise" this code and have it easily used in the validators without having to write the entire validators from scratch just so I can change the class they inherit from further down the line.

Cheers,

+1  A: 

You could override the Validate method in you page base. To add the validation dependency information in the page you can implement a not rendered control:

<my:ValidationDependency TargetControl="RegExp1" Dependency="Required1" />
onof
A: 

you can have ValidationControlToDependOn property as of type List and add the validators into the list. So we can assume that the validator added later depends upon the validator added before it.

so your protected override bool EvaluateIsValid()

will change somewhat

foreach(IValidator validator in ValidationControlToDependOn)
{
return !validator.IsValid;

}
ajay_whiz
wouldn't that make every validator on the page dependent on the previous one?And I think I would still need to subclass each validator
Daniel
A: 

I'm looking into control extenders which seems like it could be promising but I cannot find many examples of doing anything but AJAX stuff with it.

Daniel
I tried, but you can't override a method of the extended control. IMO the only way to avoid subclassing is to override Page.Validate().
onof
A: 

You might want to look into a WebControlAdapter.

Basically allows you to override certain methods of webcontrols (conditionally for some browsers if need, but here can be for all).

In your case, you would want to override the EvaluateIsValid method and check if the control has any dependency on a 'parent' validator.

As an example, a TextBox adapter we recently created to render a 'maxlength' attribute to the control.

Public Class TextBoxAdapter
        Inherits WebControlAdapter

        Private ReadOnly Property TextBoxControl() As TextBox
            Get
                Return DirectCast(MyBase.Control, TextBox)
            End Get
        End Property

        Protected Overrides Sub RenderBeginTag(ByVal writer As System.Web.UI.HtmlTextWriter)
            If TextBoxControl.TextMode = TextBoxMode.MultiLine AndAlso TextBoxControl.MaxLength > 0 Then
                writer.AddAttribute("maxlength", TextBoxControl.MaxLength.ToString)
            End If

            MyBase.RenderBeginTag(writer)
        End Sub
    End Class

To use it, just create a .browser file in your App_Browsers directory and setup the adapter there:

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="System.Web.UI.WebControls.TextBox"
               adapterType="TextBoxAdapter" />
        </controlAdapters>
    </browser>
</browsers>

The only complication that still remains in your case is how to store the dependent validator in order for the EvaluateIsValid to have access to this directory. You might consider a non-rendered control like Onof suggested or else Viewstate/Cookie/other storage mechanism.

Sam