This keeps coming up on the forums, probably because it's pretty counterintuitive. What do you think will happen if I click the button in the following example?
<%@ Page Language="C#" %>
<script runat="server">
protected void sleep(object sender, EventArgs e) { System.Threading.Thread.Sleep(5000); }
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<%= DateTime.Now %>
<asp:Button ID="Button1" runat="server" Text="Click Me!" OnClick="sleep" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
<ProgressTemplate>
Working...
</ProgressTemplate>
</asp:UpdateProgress>
</form>
</body>
</html>
Right! The UpdateProgress will show "Working..." for five seconds while the async postback completes. Okay, that was an easy one, just a warm-up. What about this code?
<%@ Page Language="C#" %>
<script runat="server">
protected void sleep(object sender, EventArgs e) { System.Threading.Thread.Sleep(5000); UpdatePanel1.Update(); }
protected void Page_Load(object sender, EventArgs e) { ScriptManager.GetCurrent(Page).RegisterAsyncPostBackControl(Button1); }
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<%= DateTime.Now %>
</ContentTemplate>
</asp:UpdatePanel>
<asp:Button ID="Button1" runat="server" Text="Click Me!" OnClick="sleep" />
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
<ProgressTemplate>
Working...
</ProgressTemplate>
</asp:UpdateProgress>
</form>
</body>
</html>
Here I've moved the Button outside of the UpdatePanel and instead made it do an async postback by calling RegisterAsyncPostBackControl(). Finally, I'm causing the UpdatePanel to update by calling Update() on it in the code-behind.
This time, the UpdateProgress doesn't display. If that surprises you, think for a minute about the order of what needs to happen:
- You click the button.
- On the client, the UpdateProgress decides whether or not to display itself. (For those hardcore geeks out there, UpdateProgress hooks the BeginRequest event on the PageRequestManager.)
- The async postback is sent back to the server.
- The server sends back the updates that should be applied to the page.
- The UpdateProgress hides itself if necessary. (This is in EndRequest.)
Now it should be clear why the UpdateProgress can't show itself in the above scenario. It simply doesn't know whether or not the UpdatePanel it's associated with is going to be updated. In this case, it's always updated, but there's no way to know that. (Maybe you check the current date and only update the panel if it's a Tuesday...)
Okay, now try this example:
<%@ Page Language="C#" %>
<script runat="server">
protected void sleep(object sender, EventArgs e) { System.Threading.Thread.Sleep(5000); }
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<%= DateTime.Now %>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="Button1" />
</Triggers>
</asp:UpdatePanel>
<asp:Button ID="Button1" runat="server" Text="Click Me!" OnClick="sleep" />
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
<ProgressTemplate>
Working...
</ProgressTemplate>
</asp:UpdateProgress>
</form>
</body>
</html>
Here I've used a trigger to say that when Button1 is clicked, Update1 should be updated. Are you surprised to learn that the UpdateProgress control doesn't display with the above code?
If you read the documentation carefully, you'll see that this is the documented behavior (emphasis added):
You can associate the UpdateProgress control with a single UpdatePanel control by setting the progress control's AssociatedUpdatePanelID property. In that case, the UpdateProgress control displays a message only when a postback originates inside the associated UpdatePanel control.
This is actually a very similar scenario to the previous example. Triggers happen on the server, not on the client. That means that when the UpdateProgress control has to decide whether or not to display, it doesn't yet know whether the associated UpdatePanel is going to be updated. It uses a simple heuristic that just checks to see if the control issuing the postback is inside the associated UpdatePanel or not. In this case, it's not, so the UpdateProgress doesn't show up.
Okay, one last example now that I have you warmed up:
<%@ Page Language="C#" %>
<script runat="server">
protected void sleep(object sender, EventArgs e) { System.Threading.Thread.Sleep(5000); }
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="false">
<ContentTemplate>
<%= DateTime.Now %>
<asp:Button ID="Button1" runat="server" Text="Click Me!" OnClick="sleep" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
<ProgressTemplate>
Working...
</ProgressTemplate>
</asp:UpdateProgress>
</form>
</body>
</html>
This is the same as the first sample except that I set ChildrenAsTriggers="false". Because of that setting, UpdatePanel1 never actually gets updated. Despite that fact, the UpdateProgress still displays, because it's using that really simple heuristic of, "Is the control inside the UpdatePanel?" The behavior you end up with is that when you click the button, "Working..." shows up for five seconds and then disappears, with no other change to the page.
There are definitely some counterintuitive scenarios here. The thing to keep in mind is that the UpdateProgress control is really not that smart, and it makes all its decisions on the client. The UpdatePanelAnimation extender in the AJAX Control Toolkit is faced with the same challenges around when to play the "OnUpdating" animation, and I believe it always behaves in the same way as the UpdateProgress control. UPDATE: Actually, the UpdatePanelAnimation "OnUpdating" animation plays on all async postbacks, so they went with the opposite default as compared to the UpdateProgress control.
What can you do about it?
I'd feel remiss if I told you all those shortcomings and didn't tell you what you can do about it. My recommendation if you need better control over when the UpdateProgress control displays is to hook the client-side BeginRequest and EndRequest events of the PageRequestManager and do whatever complicated work you want to.
For example, if you add the following script to any of the above examples, it will display the UpdateProgress control whenever you click Button1. (No need to manually hide it... the control will do that for us.)
<script type="text/javascript">
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(function (sender, args) {
if (args.get_postBackElement().id == '<%= Button1.ClientID %>') {
$find('<%= UpdateProgress1.ClientID %>').get_element().style.display = 'block';
}
});
</script>