Many times we need to validate user input with some custom logic that is so hard to implement on the client side with javascript.
There are even times when this is not possible (for example validating username uniqueness against a database).
Still we would like to avoid postbacks and give an interactive feedback to the user.
Now, we will try to use a CustomValidator leveraging WebServices.
Since the ASP.NET validation framework is synchronous, many people tried to implement WebService-based validation using synchronous calls to the server.
But we all know (or should know) synchronous IS BAD, and this is why the whole AJAX framework is implemented asynchronously.
So, how can we stop the postback if we can't have an immediate response from the webservice?
Here is my try.
Let's start from our sample page:
<asp:ScriptManager runat="server">
<Services>
<asp:ServiceReference Path="~/CustomValidator/Webservices/WebService.asmx" />
</Services>
</asp:ScriptManager>
<div>
<asp:TextBox runat="server" ID="MyTextBox" />
<asp:CustomValidator
runat="server"
ID="MyCustomValidator"
ControlToValidate="MyTextBox"
OnServerValidate="MyCustomValidator_ServerValidate"
ClientValidationFunction="myAjaxValidator"
ErrorMessage="Invalid entry!" />
<br />
<asp:Button runat="server" Text="Submit" />
</div>
Here we have our ScriptManager with a ServiceReference pointing to our validation WebService, a TextBox to be validated, the CustomValidator and a submit button.
Here is our sample validation WebService:
[WebService(Namespace =
"//tempuri.org/">http:
//tempuri.org/")][WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class WebService : System.Web.Services.WebService {
[WebMethod]
public bool Validate(string value) {
return WebService.StaticValidate(value);
}
public static bool StaticValidate(string value)
{
return value.ToLower() != "foo";
}
}
I've moved the validation logic in a static method, so it can be invoked from the server-side validation event too, enabling us to share the same code for client-side and server-side validation.
So, any value will be accepted, but "foo".
Finally, the javascript client validation code:
<script type="text/javascript">
var originalTarget;
var originalErrorMessage;
function myAjaxValidator(sender, args) {
/* invoke validation webservice with a success callback */
/* no failure callback, and the sender passed in as UserState */
WebService.Validate($get('<%= MyTextBox.ClientID %>').value, callBack, null, sender);
/* store the original target of the event */
originalTarget = $get('__EVENTTARGET').value;
/* validation is always set to false */
args.IsValid = false;
/* store the original errormessage from the CustomValidator */
if (sender.errormessage != "")
originalErrorMessage = sender.errormessage;
/* remove the errormessage, so it is not shown to the user */
/* until asynchronous WebService call is complete */
sender.errormessage = sender.innerText = "";
}
function callBack(result, sender) {
var isValid = result;
// validate all other validators on the page
for (i = 0; i < Page_Validators.length; i++)
if (Page_Validators[i] != sender) {
ValidatorValidate(Page_Validators[i]);
isValid = isValid && Page_Validators[i].isvalid;
}
if (result) {
if (isValid)
// force postback if all validators report valid data
__doPostBack(originalTarget, '');
}
else {
// display the original errormessage if the WebService result is false
sender.errormessage = sender.innerText = originalErrorMessage;
}
}
</script>
So, this is what is going to happen:
As the validation event is fired, our custom validation function invokes our WebService, passing in a reference to an asynchronous callback.
The validation is always set to false: postback will be blocked.
Before leaving the function, we store the original target of the event and the original errorMessage of our validator.
Then we clear the errorMessage of the validator, so it will not show to the user.
Finally, the asynchronous callback is invoked by the AJAX framework.
We loop through all the validators on the page and verify they are valid.
If all validators report valid values, and the WebService result is valid too, we force a postback invoking the __doPostBack() function, passing in the originalTarget we stored before.
Eventually, we restore the errorMessage for the CustomValidator if the WebService reports invalid state.
Give it a try. I've tested it with other standard validators on the page, and it seems working fine.
There is only one drawback:
I was not able to get it working when there is a ValidationSummary on the page. 
UPDATE: Seems user Nick Cipollina found a solution to have a ValidationSummary on the page!
Read his comment for further details.
Any comments and feedbacks are welcome.
Have fun coding!