Tom Clarkson is a SharePoint consultant and entrepreneur based in Sydney, Australia.

Contact Details

Links



Recent Searches



Archives




Past Posts







RSS Feed

Asynchronous Validation with ASP.NET AJAX, CustomValidator and Web Services

.NET
Thursday June 19 2008

A web form I am currently working on has a couple of simple requirements:

  • The user should be notified as soon as an incorrect value is entered.
  • The values entered need to be validated using a complicated set of rules in a back end system, available via a web service.

   

The first requirement is a perfect fit for a CustomValidator with ClientValidationFunction and the AJAX control toolkit ValidatorCalloutExtender, which is what we are using for simpler things like checking that a number field does not contain letters.

   

The second requirement doesn't fit in so well - since a database connection is involved it is impossible to do client side, and a server side validation function won't check the value until the form is submitted.

Initially I thought that I could simply call a web service from within the client side validation function. Unfortunately that isn't as simple as it seems, because the ASP.NET AJAX ScriptManager can only call web services asynchronously, while validation methods are synchronous and must set args.IsValid before exiting.

   

My next idea was to make the web service call synchronous. I found a few useful posts on this, in particular SJAX Call, but most references I found were simply "don't do it". Since adding a custom XMLHttpExecutor is way more complexity than I was looking for, especially for something with potential delay issues for the user, so I decided to try a different approach.

   

Since making the web service synchronous is both an undesirable solution and too much work, I have gone with making the validation asynchronous with a combination of caching and a custom ValidationResult object.

   

First, the server side component:

 

  [WebMethod]

public ValidationResult TestWebServiceValidation(string customValidatorId, string param1, string param2)

{

ValidationResult vr = new ValidationResult();

vr.CustomValidatorId = customValidatorId;

vr.Parameters = param1 + ";" + param2;

vr.IsValid = false;

vr.Result = "This is a test result";

return vr;

}

 

The Validation result entity is designed to be easily added to a cache, with the key being based on the validator id and the parameters (which will generally be the values of the controls being validated).

Result is an object that can be anything, as long as your client side code knows what to expect.

CustomValidatorId is passed in as a parameter and included in the returned object because otherwise it would be impossible to get the validator that triggered the call without writing a seperate callback method for every validator on the page.

   

On the client side, we start with the cache:

 

var ValidationResultCache =
				new Array();
				

 

function GetValidationResultFromCache(customValidatorId, parameters)
				{
				

for
				(i =
				0; i < ValidationResultCache.length; i++)
				{
				


				var vr = ValidationResultCache[i];
				


				if
				(vr.CustomValidatorId == customValidatorId && vr.Parameters == parameters)
				{
				


				return vr;
				


				}
				


				}
				

return
				null;
				

}

 

This is about as simple as caching gets, so it has potential issues of course, but it at least performs the basic function - returning a ValidationResult without calling the web service if it has previously been called to validate the same values.

 

Next is the validation function. It checks the cache for a validation result and calls the web service only if an appropriate result in the cache. Note that this will break rather badly if the cache key calculated here is different from the one generated by the web service method.

 

When the web service needs to be called, IsValid is set to false. This is to ensure that the form cannot be submitted while the web service call is still being processed. An alternative option is to set IsValid to true, but make sure an equivalent server side validator is in place.

   

Note that there are some things in this function that are part of the larger validtion framework. In particular:

 

  • this function is called with args.IsValid = ValidateDuplicateInvoiceNumber...
  • SetError updates the message on both the custom validator and the callout extender
  • Message 8 is "{0} is a duplicate invoice number"
  • Message 12 is "{0} is being validated - please wait"

 

function ValidateDuplicateInvoiceNumber(sender, currentRules, value, fieldDescription)
				{
				


				if
				(currentRules[8]
				!=
				null
				&& value !=
				""
				&& value !=
				null)
				{
				


				var result = GetValidationResultFromCache(sender.id, value+";param2")
				


				if
				(result ==
				null)
				{
				

        ValidationTest2.UIService.TestWebServiceValidation(sender.id, value,
				"param2", TestWebServiceSuccess,TestWebServiceFail);
				

        SetError(sender,
				12, fieldDescription);
				


				return
				false;
				


				}
				


				else
				{
				

    if
				(!result.IsValid)
				{
				

            SetError(sender,
				8, fieldDescription);
				


				return
				false;
				

            }
				

          }
				

        }
				


				return
				true;
				


				}
				


   

The callbacks passed as the last two parameters on the web service call are standard, and can be used for multiple validation web services:

   


function TestWebServiceSuccess(result, userContext, methodName)
				{
				

//alert("success - "+result);
				

ValidationResultCache[ValidationResultCache.length]
				= result;
				

var cv = document.getElementById(result.CustomValidatorId);
				

ValidatorValidate(cv,
				null,
				null)
				

}
				

 

function TestWebServiceFail(error, userContext, methodName)
				{
				

alert("Validation web service error - "+error);
				

}
				

   

When the web service call finishes running, the ValidationResult is added to the cache. The validator that originally called the web service is retrieved using the id in the ValidationResult, and ValidatorValidate is called so that the original validation method (in this case ValidateDuplicateInvoiceNumber) will run again.

 

When run the second time, the result from the cache is used, and controls can be updated as happens with a standard client side validator.

Comments

On 02 Jul 2008 02:14, Amit Choudhary said:
Hi Tom,

Nice article. I have somewhat similar requirement, so I hope you can help in that. I created a custom textbox control which is derived from the common textbox control. This control implements the IValidator interface. It has a Validate method which will be called by the system/page itself when a postback occurs as it implements the IValidator interface. Now I want that on the client side when the user change the value of this control at that time it should validate itself on the server side, and on the fly show a error (alert box) to the user if the control contains invalid data.

So here is the sequence of functionality I want... 
1) OnBlur Event of the textbox occurred at client side. 
2) In the OnBlur event handler function I should call the Validate method of this control on the server side which will validate this control and send the result. 
3) If result has errors then show an alert to the user with the error message.

I am having problem in the 2 point. I am using ASP.Net 3.5 with AJAX and C#.
On 03 Jul 2008 10:59, Tom Clarkson said:
You can use ValidatorValidate(validatorcontrol) to force validation, but it's probably easier to just create your custom control as a wrapper for a standard textbox, customvalidator and calloutextender. You need a bit of extra work to pass the properties on to the textbox rather than the span it will render as, but all the complex validation stuff will be much easier to get working.
On 03 Sep 2008 12:26, Nariman said:
Great!
On 22 Oct 2008 08:41, said:
On 12 Dec 2008 02:57, Angus said:
thanks for the article,

I took advantage of your solution but eventually abandonded it in favour of using a simpler approcah. I abandoned the customValidator entirely and replaced it with a simple label. This is simpler as you can just pass the id of a label through with the webmethod call and have the return javascript function display whatever you want in it and alert the user in whatever js based form you want.
On 01 Jan 2009 01:16, Ted said:
I gave up on the ASP.Net validation scheme, and just wrote a function that fires from the textbox's onblur event, which then calls the necessary validation web service, and manually hides/shows error message when result received.
On 21 Jan 2009 06:06, Patrick said:
This was a great post.  Thanks
On 24 Jan 2009 09:39, Nick Shel said:
A simpler approach these days might be to use the ServerSideValidationExtender that's part of the Validation Guidance Bundle... See here: http://www.pnpguidance.net/Post/ServerSideValidationExtenderASPNETServerSideValidationAJAXPartialPagePostBack.aspx
On 27 Jan 2009 05:53, Tom Clarkson said:
Agreed, anything that allows writing less javascript is a good thing. 
On 11 Feb 2009 11:43, said:
On 17 Feb 2009 10:41, said:
On 21 Jun 2009 05:12, said:
On 24 Aug 2009 09:59, Ian said:
I'm trying to implement this in my project, but I have come to a halt with the following line;
ValidationTest2.UIService.TestWebServiceValidation(sender.id, value, "param2", TestWebServiceSuccess, TestWebServiceFail);

I understand that it's calling your custom .Net method (TestWebServiceValidation) and calling the javascript function on a success or a failure, but I can't work out where the ValidationTest2 object comes from. I would imagine that it's the namespace of the web service class, but I can't seem to reference that within my own project.

Will I need to register something within the page?
Oh, and what version of the .net framework would this require? I suppose that could be an issue too :)

Thanks!
On 13 Oct 2009 01:11, said:
On 30 Nov 2009 08:43, said:

Leave a comment