Monday, July 16, 2007

Handle multiple asynchronous calls in Asp.Net Ajax complex scenarios

I have been developing an Ajaxed Asp.net application that has in some pages a heavy load of Ajax components implemented with multiple update panels containing controls that fires heavy load process in the server side. To obtain a responsive UI the challenge was that the user can initiates an Ajax request and while it is processed, can initiate another requests.
Browsing the ASP.NET AJAX Client Life-Cycle Events I found the next explanation of Event Order for Multiple Asynchronous Postbacks:
"The default behavior of asynchronous postbacks is that the most recent asynchronous postback takes precedence. If two asynchronous postbacks occur in sequence, and if the first postback is still being processed in the browser, the first postback is canceled. If the first postback has been sent to the server, the server processes the second request when it arrives and does not return the first request."

This behavour "last request wins" prevents the implementation of a full process of multiple request. Googling I found this article thats use a queue and works fine with few Ajax components but freeze the browser in the heavy Ajax request scenario. With a little tweak in the code (commented below) now it works smoothly and can handle a set of continuous requests.
WARNING: have precaution in scenarios with controls that depend of the result of the execution of another control.


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Untitled Page</title>

</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>

</div>

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Button" />
</ContentTemplate>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel2" runat="server">
<ContentTemplate>
<asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Button" />
</ContentTemplate>
</asp:UpdatePanel>

<asp:UpdatePanel ID="UpdatePanel3" runat="server">
<ContentTemplate>
<asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>
<asp:Button ID="Button3" runat="server" OnClick="Button3_Click" Text="Button" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1"
DisplayAfter="0">
<ProgressTemplate>
Updating panel1
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdateProgress ID="UpdateProgress3" runat="server" AssociatedUpdatePanelID="UpdatePanel3"
DisplayAfter="0">
<ProgressTemplate>
Updating panel3
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdateProgress ID="UpdateProgress2" runat="server" AssociatedUpdatePanelID="UpdatePanel2"
DisplayAfter="0">
<ProgressTemplate>
Updating panel2
</ProgressTemplate>
</asp:UpdateProgress>
<div id="AlertDiv">
<span id="AlertMessage"></span>
</div>

<script type="text/javascript">
Sys.Application.add_load(ApplicationLoadHandler)

function ApplicationLoadHandler(sender, args)
{
var prm = Sys.WebForms.PageRequestManager.getInstance();
if (!prm.get_isInAsyncPostBack())
{
prm.add_initializeRequest(InitializeRequest);
prm.add_endRequest(CompleteRequest);
}
}
// initialize a queue
var myQueue = new Array();

function CompleteRequest(sender, args)
{
if(myQueue.length > 0)
{ // Fire correspond event again of the item cached
// Original Code:
// $get(myQueue[0].id).click();
// Changed to:
// var control = $get(myQueue[0]);
// setTimeout('__doPostBack(\'' + control.name + '\',\'\')', 0);
var control = $get(myQueue[0]);
setTimeout('__doPostBack(\'' + control.name + '\',\'\')', 0);
Array.removeAt(myQueue, 0);
}
}
function InitializeRequest(sender, args)
{
var prm = Sys.WebForms.PageRequestManager.getInstance();
if (prm.get_isInAsyncPostBack())
{ // If it's working on another request, cache the current item that cause the request
args.set_cancel(true);
// Original Code:
// Array.add(myQueue, args.get_postBackElement());
// Changed to:
// Array.add(myQueue, args.get_postBackElement().name);
Array.add(myQueue, args.get_postBackElement().name);
}
}

if(typeof(Sys) !== "undefined") Sys.Application.notifyScriptLoaded();

</script>

</form>
</body>
</html>


using System;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

protected void Button1_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(2000);
Label1.Text = DateTime.Now.ToLongTimeString();
}
protected void Button2_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(2000);
Label2.Text = DateTime.Now.ToLongTimeString();
}
protected void Button3_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(2000);
Label3.Text = DateTime.Now.ToLongTimeString();
}
}

4 comments:

Lucas Eugenio Ontivero said...

Excelente post. Realmente muy bueno. Te felicito Tincho por el nivel.
Un abrazo.

Martín Olivares said...

Gracias Lucas, como verás son pocos posts al año pero buenos ;P

Gustavo Bonansea said...

Sí señor !! Calidad y no cantidad...... quizás yo debería hacer lo mismo :D

Anonymous said...

Gracias, señor. siga así por favor. Muy interesante y vital !!!