In December, we released the M1 build of Atlas featuring ScriptManager, UpdatePanel and extender controls, which has received pretty good feedback so far. You can read more about them in my quick tour and ScriptManager drilldown posts if you haven't had a chance to look at them yet. During the past month we've worked in parallel on creating an incremental refresh build (that went live today) that addresses key limitations, known issues, and user feedback alongside our next major release. With this M1 refresh build, you'll see a bunch of new goodies beyond key bug fixes around ScriptManager and the partial rendering/async post-back model to further enable the server-control based AJAX development model.
The improvements include support for redirects and error reporting, mechanisms to indicate progress, support for true post-backs when appropriate and more intuitive updating of UpdatePanels marked with Mode=Conditional.
Support for Redirects (302s) and Errors (500s)
We added support for redirects and errors during async post-back processing. Redirects now work as you'd expect when you call Response.Redirect back on the server. We've added some internal plumbing to have redirects prompt our client script update the browser location.
Errors are more interesting. Typically you want to inform the user of errors. We handle page errors, as well as HTTP pipeline errors inside ScriptManager. ScriptManager in turn allows you to transform errors into something that is end-user friendly (you almost never want to ship exception messages to the client, other than perhaps during development for debugging purposes). The default behavior is to send down exception text (as-is right now, but the plan is to honor custom error settings).
<atlas:ScriptManager runat="server" id="scriptManager" EnablePartialRendering="true"
onPageError="OnScriptManagerPageError" />
<script runat="server">
private void OnScriptManagerPageError(object sender, PageErrorEventArgs e) {
// Set e.ErrorMessage to something you want to send down to the client
}
</script>
On the client, the default implementation of the script is to show an alert messagebox. Its not the most friendly thing to do in the world, but it is orders of magnitude better than simply failing silently, and sending the page into a non-working state thereafter. You can customize the error display using the new ErrorTemplate property of the ScriptManager:
<atlas:ScriptManager runat="server" ...>
<ErrorTemplate>
There was an error processing your action.<br />
<span id="errorMessageLabel"></span>
<hr />
<button type="button" id="okButton">OK</button>
</ErrorTemplate>
</atlas:ScriptManager>
What we do out of the box is display this error in what looks like a modal dialog (we wash out the page with a partially opaque overlay and disable it). The only active UI are the elements inside your error template. The template mechanism allows you to design a rich error display. Something to keep in mind are the fixed "errorMessageLabel" and "okButton" ID values. ScriptManager generates XML-script that uses these special IDs to bind controls to the error message property and clear error methods of the PageRequestManager script object respectively. You could obviously write your own error handling logic and completely custom UI using script, or XML-script, but a key focus of the default functionality is to enable reasonably good behavior without requiring you to delve into client-side programming.
If you're curious on what is going on over the wire for these HTTP status codes, check out the network traffic via my Web Development Helper tool. You'll see what we do to propagate 302 codes using XMLHTTP, which otherwise attempts to internally handle it (not what you want in this case).
Support for Progress Indicators
Another very important aspect of creating a user interface is to indicate progress rather than leave the user guessing about what is happening. With the traditional post-back model, the browser's statusbar serves as a simple indicator of activity. With AJAX programming and with the partial rendering model, this default form of progress display is not applicable. In the spirit of making AJAX simple, we've added a new control, atlas:UpdateProgress, that works closely with the PageRequestManager class on the client to automatically enable progress display. Here's a basic sample:
<atlas:ScriptManager runat="server" id="scriptManager" EnablePartialRendering="true" />
<atlas:UpdatePanel runat="server" ...>
...
</atlas:UpdatePanel>
<atlas:UpdateProgress runat="server" id="updateProgress1">
<ProgressTemplate>
<img src="Progress.gif" /> Contacting Server...
</ProgressTemplate>
</atlas:UpdateProgress>
Basically UpdateProgress is a templated control you can use to customize progress display, and place anywhere on the page. You could place an animated gif to indicate activity along with some text to indicate an async post-back is going on behind the scenes. Like everything else, the server control generates XML-script on your behalf to automatically have this piece of user interface show up when the PageRequestManager is in the middle of performing a post-back, and hide it when the response has returned.
PageRequestManager only supports a single out-going async post-back. This is because the server processing and resulting view state etc. must be updated before a second post-back can be issued. You may want to offer a mechanism to allow the end-user to cancel an operation (the browser's Stop button doesn't work for async post-backs). This is very simple. Just add a button with an ID set to "abortButton" (another special ID) inside your progress template. For example:
<atlas:UpdateProgress runat="server" id="updateProgress1">
<ProgressTemplate>
<img src="Progress.gif" /> Contacting Server...
<button id="abortButton">Stop</button>
</ProgressTemplate>
</atlas:UpdateProgress>
Support for True Post-backs
Now that we've enabled post-back-less user interfaces, sometimes you do need to perform true post-backs. For example, if you have a Logout button, or if have a File Upload on your page. Another subtle case is when you have an existing user control or custom control that itself doesn't contain UpdatePanels, but does contain buttons. In the past, the default behavior of the buttons would be to cause a true post-back, resulting in updated rendering of the overall control. Now, with async post-backs, a post-back would happen, but any updates to the overall control rendering are lost.
With this refresh, we've made the model a bit more explicit. Essentially we have certain heuristics about which elements cause post-backs and which don't. The following cause async-post-backs:
- Elements corresponding to controls that are inside an UpdatePanel
- Elements corresponding to controls that are used as triggers for an UpdatePanel
- Event targets (in the __doPostBack sense) that do not correspond to HTML elements (eg. the ID corresponding to <atlas:Timer> which does not render any UI)
So if you have a control, you could either set it up inside an UpdatePanel, or preferably add it as a trigger of the UpdatePanel that needs to be updated in response to its event. In the past, triggers were mainly an optimization technique to identify what needed to be updated. Now they go beyond that and also identify when async post-backs should take place. Incidently, this behavior goes well with the overall "Trigger" naming.
If you have a control that wants to induce async post-back, that doesn't fit one of the three built-in heuristics, you can call the new RegisterAsyncPostbackControl method on ScriptManager to register your control.
You'll find this is actually much more intuitive behavior. I too at first thought this was a step back, but upon examining some more complex pages (esp. those involving WebParts) this modified more selective behavior does make sense. You may need to look at pages you've created against the December CTP and see where you might need to explicitly specify some triggers.
Automatic and Conditionally Updating UpdatePanels
We've had the Mode property on UpdatePanel that can be set to Always (default) or Conditional. Setting mode to conditional allows you to control which UpdatePanel is updated during a request as an optimization technique. In the past, an UpdatePanel marked as Conditional would update if you either called its Update() method explicitly, or if an associated trigger was activated.
We've simplified this to some degree. If a control inside an UpdatePanel causes async-post-back, that UpdatePanel is automatically selected for an updated rendering. This simplifies the case where you might have otherwise needed a whole bunch of triggers on an UpdatePanel pointing at controls within its content template (somewhat redundantly).
As usual keep the feedback and comments coming! It is really exciting to see the enthusiasm and interest around this technology.