Canceling AJAX Web Service call
There are times you need to develop a data-entry web form calling and AJAX-enabled web service to perform some complex computations that must be done on the server (for example, when a database access needed to do the computation). The computation is sometimes based on numerous data-entry fields, so when a change in one of the fields has occurred, you want to send a web service request to re-compute the data.
As a most simple example, I’ll create a form that is capable of doing an extremely difficult task of adding two integer numbers. Here is how it looks like:
And here is the underlying sophisticated HTML fragment:
<input type="text" size="4" id="numA" onchange="add()" />
+<input type="text" size="4" id="numB" onchange="add()" />
=<input type="text" size="4" id="result" readonly="readonly" />
The add() function calls the web service, which in turn adds the two numbers and returns the result. Here is the add() function:
1: function add() {
2: var a = Number($get("numA").value);
3: var b = Number($get("numB").value);
4:
5: if (!isNaN(a) && !isNaN(b)) {
6: CancelScriptService.CalcService
7: .Add(a, b, function(res)
8: { $get("result").value = res; });
9: }
10: else {
11: $get("result").value = "";
12: }
13: }
The function converts the text values it’s got from the input boxes, validates the values for being numbers (this is not a full validation, but it will do for now), and then calls the Add() web method using the proxy generated by the ScriptManger. The third argument of the web method call function is a delegate to a function that will be called upon success. To shorten the code, I’ve created a JavaScript anonymous delegate (sort of) that updates the result.
I’m omitting the ScriptManager definition, and the ServiceReference declaration. The full source of the sample is attached to this post.
And here is the ingenious web method:
[ScriptMethod]
public int Add(int a, int b)
{
return a + b;
}
Every time one the two input boxes changes, a web service call will be administered and the result will be put into the third text box. But what happens when the network is slow, or the calculation takes a couple of seconds to proceed? For the sake of the demo, I’ll suspend the current thread in the web service method for 5 seconds:
[ScriptMethod]
public int Add(int a, int b)
{
Thread.Sleep(5000);
return a + b;
}
The thing is, the MS AJAX library can handle only one async call at a time, so it queues all the subsequent calls for later execution. In our case, if we change input boxes values numerous times, we’ll see the result box updated with a new result every 5 seconds. Pretty odd, isn’t it?
To solve the problem, we somehow need to abort the active web service call when the user inputs a new value. For that purpose, the MS AJAX library includes a WebRequestExecutor object that is a member of the WebRequest object created upon every call to a web service. However, when we call CalcService.Add() proxy function, we’re actually calling a singleton instance method, which for some reason, does not return anything. Therefore, to solve it, we need to instantiate an actual CalcService object and call its Add() method. Here is a revised code:
1: var lastExecutor = null;
2:
3: function add() {
4: var a = Number($get("numA").value);
5: var b = Number($get("numB").value);
6:
7: if (lastExecutor != null && lastExecutor.get_started()) {
8: lastExecutor.abort();
9: }
10: if (!isNaN(a) && !isNaN(b)) {
11: var calcService = new CancelScriptService.CalcService();
12: var request = calcService.Add(a, b,
13: updateResult, onFailure);
14: lastExecutor = request.get_executor();
15: }
16: else {
17: $get("result").value = "";
18: }
19: }
20:
21: function updateResult(res) {
22: $get("result").value = res;
23: }
24:
25: function onFailure(ex) {
26: if (!lastExecutor == null && lastExecutor.get_aborted()) {
27: // the execution was aborted
28: return;
29: }
30: // handle the error here
31: }
The revised add() function starts with a regular number conversion and validation. Disregard lines 7-9 for one moment. On line 11, I create an instance of CalcService proxy class, and call its Add() method to initialize a web method call. Now, however, the method returns a Sys.Net.WebRequest class instance, which includes and Sys.Net.WebRequestExecutor object for the current call. I’m keeping the last instantiated Executor in a member variable, to be able to use it on the next function call.
The executor is capable of aborting the current request, and this is what I’m doing on lines 7-9. Upon changing one of the values in the input boxes, the function checks whether another web request is active, and if there is, it aborts the request, and instantiates a new one.
An important point to notice here is that when the current WebRequest is aborted, the AJAX library raises an exception, therefore, we have to handle is gracefully. That is what I’m doing on lines 26-28.
This time, the user of the application can change the input boxes’ values numerous times – the result will be updated only once – 5 seconds from the last change. Try it yourself, the source code is here:
Have fun.