I ran into a situation recently where I needed to select a set of data, present it to the user, and then give them the option to process each item individually or all items automatically. Problem was, the item processing task was long running, which complicated the “process all items” scenario. Looping through each item and processing it all on the server side would’ve been easy enough to accomplish, but the chances of the user sitting through the 7 minute postback for 20 items was next to nil.

What I really needed was incremental progress feedback, so the user could see an update as each item was processed. Luckily, I did have control over my user’s browser version and settings, so client scripting was available. With that in mind, this is the solution I came up with:


Display the Items and Interface

This sets up some test data, a repeater to display it, and the button controls to initiate processing. Note the runat and ID attributes of the body tag. This is important later.

<%@ Page Language="C#" AutoEventWireup="true" EnableEventValidation="false" CodeFile="Default.aspx.cs" Inherits="SequentialUpdates_Default" %>
<html>
<head>
    <title>Chained Updates</title>
</head>
<body runat="server" id="body">
  <form id="form1" runat="server">
    <asp:Button runat="server" ID="All" Text="All" OnClick="All_OnClick" />
    <br /><br />
    <asp:Repeater runat="server" ID="Repeater1" OnItemCommand="Repeater1_OnCommand">
      <ItemTemplate>
            <asp:Label runat="server" ID="Label" />
            <asp:Button runat="server" ID="Button" 
              Text='<%# Eval("Value") %>' 
              CommandName='<%# Eval("Key") %>' />
        <br /><br />
      </ItemTemplate>
    </asp:Repeater>
  </form>
</body>
</html>
protected void Page_Load(object sender, EventArgs e)
{
  if (!IsPostBack)
  {
    Hashtable items = new Hashtable();
 
    items.Add(1, "Item 1");
    items.Add(2, "Item 2");
    items.Add(3, "Item 3");
    items.Add(4, "Item 4");
    items.Add(5, "Item 5");
    items.Add(6, "Item 6");
 
    Repeater1.DataSource = items;
    Repeater1.DataBind();
  }
}

Event Handler and Item Processing Code

protected void ProcessItem(int index)
{
  Label lbl = (Label)Repeater1.Items[index].FindControl("Label");
  Button btn = (Button)Repeater1.Items[index].FindControl("Button");
 
  Thread.Sleep(500);
 
  lbl.Text = btn.CommandName;
}
 
protected void Repeater1_OnCommand(object source, RepeaterCommandEventArgs e)
{
  ProcessItem(e.Item.ItemIndex);
}

This code sets up the event handler for the buttons in the repeater and a fake item processing function to simulate a delay and give visual feedback.

Multiple Postback Magic

protected void EnableSubmitScript(int index)
{
  body.Attributes.Add("onload", ClientScript.GetPostBackEventReference((Button)Repeater1.Items[index].FindControl("Button"), "Multi"));
}

This returns a JavaScript postback function call reference for the Button control in the repeater item[index] and places it in body.OnLoad. For example, when called on index 0, it will return __doPostBack(‘Repeater1$ct100$Button’, ‘Multi’). Calling this JavaScript code is identical to manually clicking the button, additionally including the ‘Multi’ event argument.

Since the document’s body tag has a runat=”server” and ID attribute, we can easily add this postback call to the OnLoad event of the body. It’s important to add the script this way instead of using RegisterClientScriptBlock or RegisterStartupScript, because they will insert the client script before any controls are rendered and most browsers will continue with the next postback before waiting for the rest of the page to render. Using body.OnLoad gives the full page time to load and render, then immediately proceeds to the next postback.

Adding the ‘Multi’ event argument is necessary so that we can distinguish between a user clicking the button and the automatic chain submission events. The argument will be available via Request.Form["__EventArgument"]. With that in mind, it’s time to modify Repeater1_OnCommand to play its part in the sequence:

protected void Repeater1_OnCommand(object source, RepeaterCommandEventArgs e)
{
  ProcessItem(e.Item.ItemIndex);
 
  if (Request.Form["__EventArgument"] == "Multi")
  {
    if (e.Item.ItemIndex < Repeater1.Items.Count - 1)
      EnableSubmitScript(e.Item.ItemIndex + 1);
  }
}

What this does is check __EventArgument for the ‘Multi’ parameter. If it’s there, then this is part of a multiple submission and we need to check which index we’re on. If it’s not the last one, call EnableSubmitScript on the next index to continue the multiple submission cycle.

Finally, we need an event handler for the All button, to kick things off:

protected void All_OnClick(object sender, EventArgs e)
{
  ProcessItem(0);
 
  if (Repeater1.Items.Count > 1)
    EnableSubmitScript(1);
}

I chose to go ahead and process item 0 there, and then start the multiple submit cycle with 1 if there is more than one repeater item. This could be cleaner by simply replacing the whole thing with EnableSubmitScript(0), but it adds an extra postback to the beginning that I didn’t care for.

That’s it! Now, when the user clicks “All” it loops through each repeater item and automatically submits each one in sequence. Being able to see the process step affecting each item in real time is invaluable feedback for the user. Of course, the obvious next step is to convert the repeater items to AJAX UpdatePanels and use a ScriptManager to control the processing instead.

Code for this example: SequentialUpdates.zip