Premise

Most of our projects are (still) using ASP.NET webforms technology, where user input validation is contained within the subclasses of the System.Web.UI.WebControls.BaseValidator abstract class. Each validator emits a hidden span element (which can be styled with CSS) that contains the error message to display (the Text property) and becomes visible each time the validator is evaluated and is found to be invalid. Additionally, each validator can display a separate error message (the ErrorMessage property) which can be displayed within an also stylable ValidationSummary control. There are quite a few more properties and functionality that the validation control family has. For our purposes, we are interested in the fact that we can group the validators and corresponding validation summaries in validation groups, to overcome the single form element limitation of ASP.NET webforms, and that most of the stock ASP.NET validators (with the exception of the CustomValidator, where if required, we need to write our own) already have a client-side validation function apart from server-side.

As described above, the display of these validator controls is rather limiting. What if we want to change the control's style (e.g. change the background color and/or border)? What if we need to display extra html elements, or change the appearance of an enclosing div based on whether the validator is valid or not?

Alternatives

There are some ways to overcome this

  • AjaxControlToolkit Callout Validator control. Though it is limited in its display capabilities, it shows a way to display the validation outcome differently. Since it is open source, we could read up the source, and get an idea as to how we could create other ways to display validation errors.
  • Eschew the client side validation entirely and go with a javascript validation library (e.g. jQuery.Validate).

Neither of the above is without its issues. Creating extra server side controls for custom validation display adds a non-negligible development effort for each project that needs something custom in validation error display, and severely limits the reusability of the solution, as experience has shown that each site design has its own quirks when it comes to displaying input errors.

On the other hand, relying on a separate library to do the client side validation introduces issues where we might have differences in the outcome of the validation between client side and server side (notably when validating emails, or performing other regex-based validations). And moreover, validation groups become rather cumbersome to implement.

Solution

Luckily, ASP.NET offers a series of client-side functions and variables that can make our life easier. With the jQuery library, a little bit of reusable javascript and a couple of extra small, project-specific, javascript functions, we can have whatever error display we wish, using just the stock ASP.NET validation controls and schema.

Let’s assume the following typical markup for a form with a couple of fields.

    <form id="form1" runat="server">
        <div>
            <div class="inputOuter">
                <asp:Label ID="labelName" runat="server" AssociatedControlID="Name" Text="Name: " />
                <asp:TextBox runat="server" ID="Name" />
                <asp:RequiredFieldValidator runat="server" ID="NameRequired" ControlToValidate="Name" Text="Name Required" ValidationGroup="valGroup" />
            </div>
            <div class="inputOuter">
                <asp:Label ID="labelEmail" runat="server" AssociatedControlID="Email" Text="Email: " />
                <asp:TextBox runat="server" ID="Email" />
                <asp:RequiredFieldValidator runat="server" ID="EmailRequired" ControlToValidate="Email" Text="Email Required" ValidationGroup="valGroup" />
                <asp:RegularExpressionValidator runat="server" ID="EmailInvalid" ControlToValidate="Email" Text="Email Invalid" ValidationGroup="valGroup" />
            </div>
            <div class="buttonArea">
                <asp:Button runat="server" ID="submitButton" CssClass="submitButton" Text="Submit data" />
            </div>
        </div>
        <asp:Label ID="Result" runat="server" />
    </form>

First of all, we need to take a reference to the button, and intercept its click event:

$(function () {
    $(".submitButton").on('click', function (e) {
        //do something
    });
});

Now, we need to trigger a ASP.NET client side validation:

$(function () {
    $(".submitButton").on('click', function (e) {
        var isValid = Page_ClientValidate("ValGroup");
        if (!isValid) {
            e.preventDefault();
        }
        return isValid;
    });
});

The Page_Validate function belongs to the ASP.NET client side validation API. It forces all validators belonging to the validation group passed in as argument to check their validity status.

As mentioned earlier, we’ll be trying to make this solution as reusable as possible. So we’ll create a function and push as much generic code in there as we can. For now it’s pretty simple, but we’ll fill it up as we go along.

function ValidateAllInGroup(group) {
    var val = Page_ClientValidate(group);
    return val;
}
$(function () {
    $(".submitButton").on('click', function (e) {
        var isValid = ValidateAllInGroup("vgGroup");
        if (!isValid) {
            e.preventDefault();
        }
        return isValid;
    });
});

Ok, we have validated the form, now we need to do something with the invalid controls. To do that, we iterate over ALL validators in page, and select only those that are not valid:

function ValidateAllInGroup(group) {
    var i = 0;
    var invalidControls = {};
    // Create list of invalid controls
    for (i = 0; i < Page_Validators.length; i++) {
        if (!Page_Validators[i].isvalid) {
            invalidControls[Page_Validators[i].controltovalidate] = true;
        }
    }
    return val;
}

To do that, we have used the Page_Validators array provided by .NET, and the isvalid property of each validator within the Page_Validators array.

Now, for each validator in the page, if its validated control is in the invalid control list then we need to make style (and possibly markup) changes to reflect that the input checked was found to be invalid. Likewise, if the validated control is found to be valid, we need to undo any such changes made during a previous validation check. This is done because the input control may still be carrying the error markup and style from a previous validation.

Since we are trying to have a reusable piece of code, and the style/markup changes that need to be made to display the error are project specific, we cannot add such code in the ValidateAllInGroup function. What we can do, however, is implement these changes as external callbacks. Here’s the complete ValidateAllInGroup:

function ValidateAllInGroup(group, callbacksuccess, callbackerror) {
    var val = Page_ClientValidate(group);
    var i = 0;
    var invalidControls = {};
    for (i = 0; i < Page_Validators.length; i++) {
        if (!Page_Validators[i].isvalid) {
            invalidControls[Page_Validators[i].controltovalidate] = true;
        }
    }
    for (i = 0; i < Page_Validators.length; i++) {
        if (invalidControls[Page_Validators[i].controltovalidate]) {
            $("#" + Page_Validators[i].controltovalidate).on("change.validator", function () {
                var isvalid = true;
                for (var j = 0; j < Page_Validators.length; j++) {
                    if (Page_Validators[j].controltovalidate == jQuery(this).attr("id")) {
                        ValidatorValidate(Page_Validators[j]);
                        ValidatorUpdateDisplay(Page_Validators[j]);
                        if (!Page_Validators[j].isvalid) {
                            isvalid = false;
                            callbackerror(jQuery(this));
                        }
                    }
                }
                if (isvalid) {
                    callbacksuccess(jQuery(this));
                }
            });
            callbackerror("#" + Page_Validators[i].controltovalidate);
        }
        else {
            $("#" + Page_Validators[i].controltovalidate).off(".validator");
            callbacksuccess("#" + Page_Validators[i].controltovalidate);
        }
        ValidatorUpdateDisplay(Page_Validators[i]);
    }
    return val;
}

You’ll notice that we’ve added quite some code there. Let’s iterate over the main points.

For every validator, if the corresponding validating input control is found invalid (even by another validator acting on it), we add a jQuery event under a namespace, that’s triggered when the input value changes. Effectively, when a control is found to be invalid, we ask to recheck its validity on each change and act accordingly. If the control is found valid, we remove the event.

The reason we’ve added a namespace is to easily add and remove these events, without affecting other events on the input that we may have added elsewhere in our javascript code (e.g. we may have a character counter that’s updated whenever the value changes. We want that event handler to remain, but we want the handler that rechecks the input data validity to be removed once the input is found valid).

The two callback functions, callbackerror and callbacksuccess both take the validated input control as argument and are called whenever we want to display the error markup and style, and whenever we want to remove it, respectively. Their implementation is project-specific, therefore we’ll be implementing them as anonymous functions within the button click handler.

Finally, the stock ASP.NET client validation API function ValidatorUpdateDisplay triggers the change in the display of the validator it takes as argument (effectively, it toggles the visibility of the span emitted by the validator).

And here’s the complete button click handler:

$(function () {
    $(".submitButton").on('click', function (e) {
                var isValid = ValidateAllInGroup("valGroup",
                    function (elm) {
                        $(elm).closest('.inputOuter').removeClass("inputOuterError");
                    },
                    function (elm) {
                        $(elm).closest('.inputOuter').addClass("inputOuterError");
                    });
                if (!isValid) {
                    e.preventDefault();
                }
                return isValid;    });
});

What we are doing in the two callbacks, is toggle a css class in a parent div. With the proper styling, e.g.

.inputOuter {
    padding:1em;
    width:30em;
    border:solid 1px white;

}

.inputOuterError{
    background: pink;
    border:solid 1px red;
}

The final outcome is as follows

Initial Load:

Fields before validation

Button clicked, errors found:

Fields with validation triggered by button click

Of course, we can do much more in the callback functions:

Example with errors as tooltips

Example with hilighting and validation summary

Example with border color and text below the field

Addendum

You’ll notice that the source code contains a few extra lines of code:

jQuery(".formArea").find("input[type='text'], select, textarea").each(function () {
            jQuery(this)[0].onchange = null;
        });

In this particular example, we are doing that to invalidate the .NET handler that is bound to the change event, and triggers the stock ASP.NET control validation procedure (therefore not executing our custom code), to prevent an inconsistent look in our validation scheme.

NetValidationJQuery.zip