Saturday, June 14, 2008

A look at canceling a page method call.

Last year I did some posts on ASP.Net AJAX and how to use it.  The posts were based on my experiences of using it up to that point.  Since then, I've been busy on a lot of different project and am finally circling back around to ASP.Net AJAX. 

I ran into an issue a couple weeks ago, now, that caused more research time than I was hoping for.  The scenario was simple. A user types in an ID into a removal form that would, in essence, remove the record from the database.  Instead of blindly "deleting" the record, the new update was to query the record and provide information about it into a new confirmation-like window.  At that time the user can commit the removal or opt to not do it.  While querying and processing, a "cancel" button was to be placed to prevent the current action from taking place.

This sounds simple enough.  In order to boost page performance (the page is already littered with legacy practices that need to be cleaned up), I opted to implement page methods to do the actions instead of Update Panels.  This made a noticeable difference; however, it raised the question of how do I cancel an asynchronous page method call.

After a handful more hours than I had hoped, I still couldn't find the answer already posted.  Then I finally located this post that was doing what I wanted using Web Services.  In it, Kazi Manzur Rashid Amit, does a great job in explaining the technique required cancel a web service call.  Since Enabling Page Methods cause the page's public static methods to be exposed as web methods, I thought they were the same; however, there seems to be a slight difference which I'll mention after the I through the process.

First thing's first.  If you are interested on how to setup Page Methods, please refer to my Introduction to Page Methods Post for instructions on how to begin working with them.  The method described there is very straight forward and works well for a lot of situations.  In order to call any page method from the JavaScript code, you simply have to add a line like the following:

PageMethods.myStaticPageMethod(param, onSuccess, onFailure)

This is very simple.  The "myStaticPageMethod" is the name of the page method in the code behind file and accepts a parameter, identified by "param".  If the method call is successful, the JavaScript method "onSuccess" will be called while "onFailure" will be called if it wasn't successful.  Again, this is very simple.

Now, where the issue comes in, which Amit's blog points out, is that this "myStaticPageMethod" syntax is references a void method (or a method that doesn't return any value or object).  While the page method's code may pass a value into the "onSuccess" or "onFailure" methods, the actual page method call returns no value. In order to have the method return a value, or to be more specific the instance of the request object, we need to change the syntax slightly.

PageMethods._staticInstance.myStaticPageMethod(param, onSuccess, onFailure)

This snippet is identical to the previous one with the exception that we've added the "_staticInstance" piece between the object name and method name.  Like web service calls, when the ASP.Net AJAX Script resources create the JavaScript object prototypes for Page Methods that get passed to the client, they also create static instances of the object that return the request object.  This request object can then be set to a variable and used to cancel the call if required.

Since my requirement was to have a cancel button during processing, I had to use the static instance version of the page method.  In addition, I set the method call's request to a global JavaScript variable so that it could be references by other functions (i.e. the button click event).  So up to this point, my code looks something like this:

var request; //outside of any function definition
request = PageMethods._staticInstance.myStaticPageMethod(param, onSuccess, onFailure) //inside my functions

Now, that I have the request object assigned, I need to cancel the request.  To do this, Amit's blog pointed me to the WebRequestExecutor object.  Microsoft's documentation on the WebRequestExecutor object can be found here.  This object allows for web requests to be queried and acted upon.  For our goal, we want to utilize its "abort()" method; however, we first have to get our request's executor.  To do this, we call the request's "get_executor()" method.

var executor = request.get_executor();

After we have the executor object, we have to ensure that it's still started.  The WebRequestExecutor has a started property and to access it's value, the API has a "get_started()" method.  If the method returns true, then the executor has started processing our web request else it hasn't yet or has already finished.  Once we have identified that the executor has started processing our request, we can then abort it.  Below is the code snippet that illustrates this.

if (executor.get_started())
{
    executor.abort();
}

Here's where I saw a difference between my method and the code posted on Amit's blog.  When I called the abort method, my "onFailure" method was being called and providing the error message "The server method 'myStaticPageMethod' failed."  When I ran Amit's code which uses Web Services, I didn't see this error.  I haven't looked through the code thoroughly to see if the error is being hidden or just not bubbling so I there may not be a true difference here.  Regardless, I ended up throwing together a small "hack" to prevent the error for being shown to the end user each time a request was canceled.

Since I could not prevent the error, I ended up having to hide it.  I don't like this solution; however, I haven't been able to find a better one.  If anyone knows of a better, cleaner way please let me know.  In order to hide the error, I declared another global variable, similar to the request variable of these examples.  I named the variable "aborted" since it would signify whether or not the request has been or not by my code.  Afterwards, I just assigned it the boolean value of true before I abort the call.

var aborted = false; //placed outside of any function

//modified aborted snippet
if (executor.get_started())
{
    aborted = true;
    executor.abort();
}

Now that I had an indicator when a user attempted to manually abort a method call, I could add the following into my "onFailure" function to prevent the error from appearing.

if (aborted == true)
{
    aborted = false;
    return;
}

So, after all of that, we have done the following to add the functionality of canceling a Page Method to our code:

  1. Declared the "request" global variable
  2. Declared the "aborted" global variable
  3. Set the "request" variable to the WebRequest object returned by the static instance version of our page method
  4. Set the "request" variable = null at the beginning of each method to prevent confusion
  5. Created an "executor" variable in our cancel request method.
  6. Set the "executor" variable to the WebRequest's executor property value
  7. Ensured the "executor" variable has started processing our request
  8. If started, set the "aborted" variable to true
  9. If started, called the "executor" variable's "abort()" method
  10. Stopped the failure method from fully processing the error by returning if the request was aborted.

That's a lot of steps; however, there may be a better way.  Again, if there is an overall better way or at least a better way to prevent that error message when aborting a PageMethod request, please let me know.


kick it on DotNetKicks.com

2 comments:

  1. Excellent Article. Thanks!

    I followed the link you provided to the Sys.Net.WebRequestExecutor Class. If you check out the members, there is an "aborted" property. In my error handler, I just check the executor's get_aborted() result. If it's true, I handle it as an aborted request. If it's false, it's an actual error.

    ReplyDelete
  2. Thank You Sir......

    Excellent article ....

    It saves my lots of work and time

    Thank You

    ReplyDelete