Cutting Edge

Canceling Server Tasks with ASP.NET AJAX

Dino Esposito

Code download available at: Cutting Edge 2007_08.exe(167 KB)

Contents

Formalizing Remote Tasks
Canceling Tasks the Easy Way
Inside the abortPostBack Method
Designing an Interruptible Task
The Client Code
Transactions
Generating the Bar

Last month I built a framework to monitor ongoing server-side tasks from the client. Using this framework, which I’ll call Progress Monitor Framework (or PMF), you can provide Web users with information about the progress of operations running on the server, something that typically requires a lot of custom code to do. With PMF, you can have a server-side task register the current state of its operation (as a percentage or as an estimate of the time left) and a client service to ping the server to read that state information. Once the state information is downloaded to the client, which happens out-of-band, updating the UI is a breeze.

Some early feedback about last month’s column pointed out two potential enhancements. The first concerns whether PMF can be used to stop ongoing server tasks, and the second looks for a better way to generate the markup for the progress bar.

Formalizing Remote Tasks

A remote task is a piece of code that executes on the server in response to a client event. There are three possible ways for an ASP.NET AJAX client page to trigger a remote task: by causing a postback managed by an UpdatePanel control, by invoking a method directly on the application’s back end exposed through a local Web service, and by using a page method. Soon there will be a fourth method: a Windows® Communication Foundation (WCF) service.

Once the task on the server has been triggered, the client no longer has control over it. The client page regains control over the operation only after the response generated by the task has been downloaded to the client and parsed. With PMF you can read the status of the task on the fly, but there is no mechanism for passing data to the server task dynamically.

Canceling Tasks the Easy Way

ASP.NET AJAX can make canceling remote operations really easy, but there are two restrictions. First, the task must have been started using UpdatePanel. Second, no extra work should be required on the server to compensate for the abrupt interruption of the task. Figure 1 shows the source code of a sample UpdatePanel-based page that pops up a progress template with a Cancel button, as shown in Figure 2. By clicking the button, you cancel the operation. Or do you?

Figure 1 Canceling a Task Started via an UpdatePanel Postback

<html xmlns=”https://www.w3.org/1999/xhtml” >
<head runat=”server”>
    <title>Canceling tasks</title>
    <style type=”text/css”>
        #UpdateProgress1  {
            width: 270px; background-color: #ffff99; height:120px;
            top: 40%; left: 35%; position: absolute;
            border: solid 1px black;
        }
        #ProgressTemplate1  {
            font-size: 9pt; color: navy; font-family: verdana; 
        }
    </style>
</head>

<script language=”javascript” type=”text/javascript”>
function abortTask()  {
    var obj = Sys.WebForms.PageRequestManager.getInstance();
    if (obj.get_isInAsyncPostBack()) 
        obj.abortPostBack();
}
</script>

<body>
<form id=”form1” runat=”server”>
    <asp:ScriptManager ID=”ScriptManager1” runat=”server” />
    <asp:UpdatePanel ID=”UpdatePanel1” runat=”server” 
            UpdateMode=”Conditional”>
        <ContentTemplate>
            <asp:Button runat=”server” ID=”Button1” Text=”Start Task ...” 
                onclick=”Button1_Click” />
            <hr />
            <asp:Label runat=”server” ID=”Label1” /><br />
        </ContentTemplate>
    </asp:UpdatePanel>
    <hr />

    <asp:UpdateProgress runat=”server” ID=”UpdateProgress1”>
        <ProgressTemplate>
            <div ID=”ProgressTemplate1”><p style=”margin:5px;”>
                <img alt=”” src=”Images/indicator.gif” 
                    align=”left” />  
                <span id=”Msg”>Your request has been submitted and 
                    it may take a while to complete.
                <br /><br />Please, wait ... </span> 
                <p align=”center”>
                <input type=”button” value=”Cancel” 
                    onclick=”abortTask()” /></p>
            </div></p>
        </ProgressTemplate>
    </asp:UpdateProgress>
</form></body></html>

Figure 2 Progress Template with a Cancel Button

Figure 2** Progress Template with a Cancel Button **(Click the image for a larger view)

As you can see in the abortTask function in Figure 1, the progress template contains a client button bound to some JavaScript code. The first thing the function does is retrieve the page request manager. In the Microsoft® AJAX client library, the PageRequestManager object is the nerve center of partial rendering. Upon page initialization, the page request manager registers a handler for the form’s submit event. In this way, the request manager is involved each time the page is going to post back. At this point, the request manager makes a copy of the request’s body as prepared by the browser and runs it through the current HTTP executor—by default, the popular XMLHttpRequest object.

The page request manager sets up the eventing model of partial rendering and tracks the ongoing operation. If there’s any pending operation, then the Boolean property isInAsyncPostBack will return true.

When the user clicks the Cancel button shown in Figure 1, the page request manager aborts the current request through its abortPostBack method. The page request manager is a singleton object, meaning that all calls are conveyed to just one instance. The reason this is so is tightly related to the mechanics of partial rendering. Partial rendering consists of sending a page request that goes through the usual processing on the server (except for the rendering stage). Among other things, this means that the viewstate is sent over and used to rebuild the last known good state of server controls. Postback and state-changed events are fired regularly, and the viewstate is updated based on these operations. The updated viewstate is then sent back along with the partially modified markup.

Because of the viewstate, two asynchronous postback calls from the same page need to be serialized, and only one at a time is allowed to run. For this reason, the abortPostBack method on the page request manager has no need to figure out which request you want to stop—there’s at most one pending request.

Inside the abortPostBack Method

Let’s briefly take a look at the source code of the abortPostBack method on the PageRequestManager class:

function Sys$WebForms$PageRequestManager$abortPostBack() 
{
    if (!this._processingRequest && this._request) 
    {
        this._request.get_executor().abort();
        this._request = null;
    }
}

If there’s a pending request, the manager instructs the executor of the request to abort. The executor is a JavaScript class that inherits from Sys.Net.WebRequestExecutor and takes care of sending the request and receiving the response.

In the Microsoft AJAX client library, there’s only one executor class—the Sys.Net.XMLHttpExecutor class—and it uses the XMLHttpRequest object to execute a request. In brief, when the preceding code calls the abort method, it basically tells the XMLHttpRequest object to abort. Put another way, it simply tells the socket through which the executor receives response data that it must close.

Now, let’s suppose that the remote task performs a disruptive action on the server. For example, let’s say that the user is given a chance to delete a few records from a database table by clicking a button. Attempts to cancel the operation via the process described above doesn’t actually stop the server operation. All it does is close the socket through which you would receive a confirmation message. The abortPostBack method on the PageRequestManager object is merely a client-side method that has no effect on what’s going on in the server.

Designing an Interruptible Task

For the abort request to be effective on the server operation, the task must be interruptible. In other words, the task has to periodically check for instructions from the client that tell it to exit. A bidirectional version of PMF would help.

When I first implemented PMF, the client and server elements of the framework shared a common data container that the server used to write data about its progress and the client used to read that data, in order to update the user interface. Some enhancements are required to make the server code receive and process dynamic client feedback such as a click on the Cancel button.

The progress server API is now based on this contract:

public interface IProgressMonitor
{
    void SetStatus(int taskID, object message);
    string GetStatus(int taskID);

    bool ShouldTerminate(int taskID);
    void RequestTermination(int taskID);
}

I’ve added two new methods: ShouldTerminate and RequestTermination. The former returns a Boolean value indicating whether the ongoing task should be terminated. The RequestTermination method represents the entry point in the API for clients wanting to stop a task. When invoked, the method creates a task-related entry in the data container (the ASP.NET Cache) that ShouldTerminate checks to determine whether interruption was requested.

The IProgressMonitor interface defined above dictates the expected behavior of an application on the server. You can implement it in a variety of classes that may use distinct data containers. I created a sample class using the ASP.NET cache named InMemoryProgressMonitor (see Figure 3). Figure 4 provides a brief description of each method. As I discussed last month, the remote task calls SetStatus repeatedly to track its current execution status and mark its progress. To support dynamic interruption, the same task will periodically invoke ShouldTerminate to be informed of client requests to quit. Figure 5 shows the typical structure of a monitorable and interruptible task.

Figure 5 Monitorable and Interruptible Remote Method

[WebMethod]
public static string ExecuteTask(int taskID)
{
    InMemoryProgressMonitor progMonitor = new InMemoryProgressMonitor();

    // Preliminary check    
    if (progMonitor.ShouldTerminate(taskID))
        return “Task aborted--0% done”;

    // First step
    progMonitor.SetStatus(taskID, “0”);
    DoStep(1);
    if (progMonitor.ShouldTerminate(taskID))
        return “Task aborted--5% done”;

    // Second step
    progMonitor.SetStatus(taskID, “5”);
    DoStep(2);
    if (progMonitor.ShouldTerminate(taskID))
        return “Task aborted--45% done”;

    // Third step
    progMonitor.SetStatus(taskID, “45”);
    DoStep(3);
    if (progMonitor.ShouldTerminate(taskID))
        return “Task aborted--69% done”;

    // Final step
    progMonitor.SetStatus(taskID, “69”);
    DoStep(4);

    if (progMonitor.ShouldTerminate(taskID))
        return “Task aborted--100% done”;

    return “Task completed at: “ + DateTime.Now.ToString();
}

Figure 4 IProgressMonitor Interface

Method Description
GetStatus Reads the current status of the specified task from the internal data storage and returns it to the client as a string.
SetStatus Invoked from the task, it writes the current status of the task to the internal data storage. The status is expressed as an object.
RequestTermination Creates a task-related entry in the internal data storage to indicate that the client placed a request for termination.
ShouldTerminate Returns true if the client placed a request to terminate the specified task.

Figure 3 InMemoryProgressMonitor

public class InMemoryProgressMonitor : IProgressMonitor 
{
    public const int MAX_TIME_MINUTES = 5;

    // Sets the current status of the task
    public void SetStatus(int taskID, object message)
    {
        HttpContext.Current.Cache.Insert(
            taskID.ToString(), message, null,
            DateTime.Now.AddMinutes(MAX_TIME_MINUTES), 
            Cache.NoSlidingExpiration);
    }

    // Reads the current status of the task
    public string GetStatus(int taskID)
    {
        object o = HttpContext.Current.Cache[taskID.ToString()];
        return o == null ? string.Empty : (string)o;
    }

    // Captures any user feedback (i.e., abort button clicked)
    public bool ShouldTerminate(int taskID)
    {
        string taskResponseID = GetSlotForResponse(taskID);
        return HttpContext.Current.Cache[taskResponseID] != null;
    }

    // Sets the task for termination
    public void RequestTermination(int taskID)
    {
        string taskResponseID = GetSlotForResponse(taskID);
        HttpContext.Current.Cache.Insert(
            taskResponseID, (object) false, null,
            DateTime.Now.AddMinutes(MAX_TIME_MINUTES),
            Cache.NoSlidingExpiration);
    }

    private string GetSlotForResponse(int taskID)
    {
        return String.Format(“{0}-Response”, taskID);
    }
}

The method featured in Figure 5 orchestrates the various steps that make up the remote task. The task can be part of the application’s middle tier and can be implemented as a workflow. It must be articulated in individual steps so that the client can plug into it to read status and request termination.

The Client Code

The client JavaScript code that triggers the remote task remains mostly unchanged from last month’s. You can use a page or a Web service method to start a task such as the ExecuteTask method in Figure 5, or run the server code through an UpdatePanel region:

<asp:UpdatePanel runat=”server” ID=”UpdatePanel1”>
    <ContentTemplate>
        <asp:Button runat=”server” ID=”Button1” Text=”Start Task ...” 
             OnClick=”Button1_Click” />
        <hr />
        <asp:Label runat=”server” ID=”Label1” /><br />
    </ContentTemplate>
</asp:UpdatePanel>

In the Button1_Click event handler, you define the remote task and make it call into a progress monitor object and the SetStatus and ShouldTerminate methods. To abruptly terminate a remote task, you add a Cancel button to the progress template—either an UpdateProgress control or a user-defined <div> block. This time, though, the click handler of the Cancel button doesn’t point to the abortPostBack method on the page request manager. Instead it points to your own abort method in the client progress API:

<script type=”text/javascript”>
var progressManager = null;
var taskID = null;

function pageLoad() {
   progressManager = new Samples.PMF2.Progress();
}

function abortTask() {
    progressManager.abortTask(taskID);
}

...
</script>

Let’s take a look at the modified client progress API. Coded in the progress.js file, this API must be linked to each ASP.NET AJAX page where you plan to use interruptible or monitorable tasks:

<asp:ScriptManager ID=”ScriptManager1” runat=”server” 
    EnablePageMethods=”true”>
    <Scripts>
        <asp:ScriptReference path=”random.js” />
        <asp:ScriptReference path=”progress.js” />
    </Scripts>
</asp:ScriptManager>

The random.js file is a dependency of progress.js and defines a method that generates random numbers for tasks. To track the status of a remote task from the client, you poll the server at regular intervals. To stop an ongoing task or, more precisely, to place a request to stop a task, you invoke a server method exposed by the progress monitor server API as part of the application’s back end:

// Cancel the operation
function Samples$PMF2$Progress$abortTask() { 
    PageMethods.TerminateTask(_taskID, null, null, null);
}

I opted for page methods to expose this client-callable function. Figure 6 provides an architectural view of the overall solution.

Figure 6 Bidirectional Progress Monitor Framework

Figure 6** Bidirectional Progress Monitor Framework **(Click the image for a larger view)

When the user clicks the Cancel button, an out-of-band call is triggered to execute the TerminateTask method defined as a page method on the codebehind class of the page. The TerminateTask method creates a task-related entry in the internal data storage (the ASP.NET cache). This entry is named after the task ID appended with a "Quit" suffix. A task designed to be interruptible checks this entry at various stages during its execution. If the entry is found, the server task aborts (see Figure 7).

Figure 7 The User Clicked the Cancel Button, Ending the Server Task

Figure 7** The User Clicked the Cancel Button, Ending the Server Task **(Click the image for a larger view)

Implemented in this way, task cancellation is more effective. If you simply abort the client postback during an UpdatePanel refresh, all that happens is that the client socket through which you receive responses is closed down. There is no impact on the code running on the server. There’s also no built-in way to programmatically stop a remote call to a Web service or page method. In this case, the JavaScript proxy class completely hides the request object being used to push the call. Although the request object and its executor have an abort method, there’s no way to find a reference to it in the context of service method call.

In the end, if you need to allow for remote task control, the Progress Indicator pattern is the only way to go. You set up a parallel channel to monitor the status and to pass further information such as a quit command, to the running task. The same architecture allows the client to change parameters on the fly or to request additional operations. The bidirectional progress monitor framework is a duplex channel that a server task and its JavaScript client can use to exchange data in the form of messages.

Transactions

So far I created a framework to monitor and stop ASP.NET AJAX tasks. It is essential to note that the framework simply notifies the task that the user requested that it terminate. If properly designed, the task promptly stops and returns. But what happens to the work it has done already?

In general, when a task is abruptly interrupted, it should undo any changes it has made and return. But the progress monitor framework can’t make that happen. If you wrap the remote task in a transaction, however, you can roll it back once the task has been interrupted, assuming the work done only touches transactional resources, like a SQL Server™ database. Another option is to use a workflow. In that case, you’d wrap the task in a TransactionScope activity and use a Code activity to set the current status and check for any termination request. If the task has to terminate, you throw an exception and automatically cause the transaction to roll back. (See the June 2007 Cutting Edge column for more information about transactional tasks in Windows Workflow Foundation.)

Unfortunately, not all operations can easily be rolled back automatically. In general, you can implement the task within a TransactionScope block and safely and effectively use any objects that implement the ITransaction interface. If you do so, all of these objects will roll back or commit accordingly. And each of these objects will know how to undo its changes.

The bottom line is that monitoring the progress of a remote task from the client is relatively easy and doesn’t have significant side effects. The PMF adds some good abstraction on top of it and provides some readymade programming tools. Making a task interruptible poses additional issues especially when the task has an inherent transactional semantics. Writing the code that just notifies the task that the user requested it to terminate is the simplest part of the game. The real complexity lies in the task implementation and its compensation policy.

Generating the Bar

To top off the article, let’s see how you can use JavaScript to easily generate progress bar markup and make it more maintainable. The progress bar you see in Figure 7 is obtained by composing HTML tables, as shown here:

<table width=”100%”>
  <tr>
    <td>69% done</td>
  </tr>
  <tr>
    <td bgcolor=”blue” width=”69%”> </td>
    <td width=”31%”></td>
  </tr>
</table>

The table contains two rows—for the companion text and the gauge. The gauge is rendered using a two-cell row where cells are given background colors and proportional widths.

If you take a close look at the preceding markup, you can identify at least three parameters—the message to the user, the value to represent, and the colors to use for the "done" and "to-do" regions. Instead of generating the markup as a string, wouldn’t it be neater to create a JavaScript class? The code in Figure 8 shows the primary method of the Samples.GaugeBar class.

Figure 8 Samples$GaugeBar$generateMarkup

function Samples$GaugeBar$generateMarkup(text, perc) {
    var builder = new Sys.StringBuilder(“”);
    builder.append(“<table width=’100%’><tr><td colspan=’2’> ”);
    builder.append(text);
    builder.append(“</td></tr><tr><td bgcolor=”);
    builder.append(this._doneBackColor);
    builder.append(“ width=’”);
    builder.append(perc + “%’>”);
    builder.append(“ </td><td bgcolor=”);
    builder.append(this._todoBackColor);
    builder.append(“ width=’”);
    builder.append(100-perc + “%’>”);
    builder.append(“</td></tr></table>”);
    
    return builder.toString();
}

The method takes text and a percentage and returns an HTML table with two rows. The top row just displays the text; the bottom row is articulated in two cells of different colors.

The markup string is built using the JavaScript version of the Microsoft .NET Framework StringBuilder object. Defined in the Sys namespace, the JavaScript StringBuilder object has a programming interface similar to its .NET Framework counterpart. You send text to StringBuilder’s internal buffer, and then output the text using the toString method.

The Samples.GaugeBar class has one method, generateMarkup, and properties such as background color for the "done" and "to-do" areas and the foreground color for the companion text. For performance reasons, the class operates as a singleton. The class is not very big, but still there’s no reason to create a new instance of it every time you need to update the bar. As a result, you define a static instance of the class and add a few static methods and properties:

Samples.GaugeBar.registerClass(‘Samples.GaugeBar’);
Samples.GaugeBar._staticInstance = new Samples.GaugeBar();
Samples.GaugeBar.generateMarkup = function(text, perc) { 
   return Samples.GaugeBar._staticInstance.generateMarkup(text, perc);
}

You have a number of options for customizing the gauge bar. To change colors, do the following:

Samples.GaugeBar.set_DoneBackColor(“#ff00ee”);
Samples.GaugeBar.set_TodoBackColor(“#ffccee”);

Likewise, you can add a nice 3D effect by defining an outset border style on the "done" cell of the table, like so:

if (this._effect3D)
    builder.append(“ style=’border:outset white 2px;’”);

Creating a class to expose functionalities makes JavaScript programming significantly more manageable. If you ever had to deal with Dynamic HTML behaviors a few years ago, you know what I mean. The Microsoft client AJAX library is a big step forward as it enables you to write complex JavaScript code much more easily. Most AJAX professionals would probably agree that for powerful AJAX programming, richer JavaScript capabilities are a must.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino at cutting@microsoft.com or subscribe to his blog.