Back Button Support for Atlas UpdatePanels

This post introduces a prototype of a server control for implementing back button and bookmarkability in pages using UpdatePanel and partial rendering.

I finally got around to putting together a download for a prototype server control I wrote for a conference demo that allows you to enable logical navigation and bookmarkability (better known as "fix the back button problem") when you start using UpdatePanels and partial rendering. I've been meaning to do this for a while, and got enough requests internally and externally, that I thought I should put together what I have right now.

There are a couple of things that are hacky in the current implementation. There is work going on in core the UpdatePanel infrastructure that will remove the limitations I am working around right now in a future build. The code for the control, its associated script, and a very basic sample page can be downloaded here.

There are basically a few steps to follow in order to use this control.

Add the History control to your page
Simply add the following:

<nk:HistoryControl runat="server" ID="history" OnNavigate="OnHistoryNavigate" />
I'll cover the implementation of the event handler in a bit. Of course you also need a ScriptManager, UpdatePanels - those go without saying at this point.

Define your logical "views"
The idea is each logical view is added as an entry into the history, and can be navigated to directly (for example via a bookmark). Given this definition, it is important to note that it doesn't represent the in-memory state of a page which really cannot be bookmarked in the general case. It represents specific states that can be uniquely identified with a small string value. Secondly, the point isn't to add all post-backs back into history. That would be as bad as the experience one has with regular post-backs. Instead, the goal here is to leverage the fact that AJAX updates to a page don't contribute to history, and now we can add just the meaningful steps to the history when a user action such as clicking a button etc. switches the page from one logical view into another.

Add your History Entries
Lets say you have a hyperlink or a button that is used to switch views (imaging a selector control, like a TabStrip or other kind of navigator control). Typically you handle a server-side event such as Click, SelectedIndexChanged etc. to perform the work of actually switching content. You've taken care of associating these events with triggers etc. to cause the right UpdatePanels to update (I'll assume that). Now in addition to causing the UpdatePanels to update, the goal is to add a history entry. In the example, I have a drop-down that switches views, so I have the following event handler:

private void OnContentListSelectedIndexChanged(object sender, EventArgs e) {
   ...
   history.AddEntry(contentList.SelectedIndex.ToString();
}
For the sake of the demo, I am just using the selected index of the dropdown list as the history identifier. If you're doing this for real, it would be wise to use a more resilient unique identifier that is valid for longer periods of time (remember the bookmarking scenario).

Handle Navigation
Now that you've added history entries, you'll see the back button getting enabled as your page updates, and entries added to the list, and the end-user can now use to navigate backwards (or forwards thereafter). The final step is to handle those navigations and change the page rendering in response.

The script on the page detects this, and triggers a post-back. In response to this post-back, the History server control raises its Navigate event, and this is where your navigation event handler comes into play. Here's the implementation from my demo:

private void OnHistoryNavigate(object sender, HistoryEventArgs e) {
    int selectedIndex = 0;
    if (String.IsNullOrEmpty(e.Identifier) == false) {
        selectedIndex = Int32.Parse(e.Identifier);
    }
    contentList.SelectedIndex = selectedIndex;
    // Update the content being displayed in the page

    // Mark the update panels as needing an update
    mainUpdatePanel.Update();
}
Basically the idea is you take the incoming history identifier (validate it, which I don't in the sample) and use it to identify the corresponding logical view, switch the page to reflect that view, and finally make sure those parts of the page get updated by calling Update on the right set of UpdatePanels.

The approach in this demo is geared toward the server-centric model where application logic runs on the server, and typically works against server controls. For the curious, check out the script in History.js. It does a few things - it uses a hidden <iframe> in IE to implement history (this isn't needed on Mozilla). It updates the URL in the address bar (adds the history identifier after the # separator into the URL), which makes it bookmarkable. It then tracks changes to the URL using a timer to detect user navigation. Upon initial page loads, it checks for the presence of a history identifier, in case the page was being loaded from a bookmark. Of course, the server control-based approach hides away all this complexity behind a simple two part API: AddEntry and the Navigated event.

If you're writing an application using the client-centric model, where you typically implement your UI logic to run on the client, you'll want to be able to add history entries and handle navigation events as part of your client-side UI logic in script rather than back on the server. If you're interested in seeing that in action, check out the latest build of Script# (0.1.6.0) which has history and back button support implemented as a first-class feature integrated deeply into the Application object on the client along with a sample.

Finally, this is an early prototype, so if you have suggestions on the code, technique or overall scenario, feel free to put down your comments below.

Update, 2/5/07: I have blogged about an updated version of this control that is sync'd to released version of ASP.NET AJAX that you will want to check out.


[ Tags: | | | | ]
Posted on Sunday, 9/10/2006 @ 6:28 PM | #ASP.NET


Comments

28 comments have been posted.

Kevin Dente

Posted on 9/11/2006 @ 7:34 AM
Hi Nikhil. Very cool! One thing I'm confused about though. Is the history stuff usable from the client-side with just Atlas? Or only with Script#?

Nikhil Kothari

Posted on 9/11/2006 @ 8:32 AM
The sample I posted above is for use with Atlas via the server control model - the APIs for interacting with and handling history are surfaced on the server. The script# implementation is separate, and is designed for client-centric programming, i.e offers public APIs for use from script. Hope that clarifies things...

Kevin Dente

Posted on 9/11/2006 @ 8:45 AM
Yep, it does, thanks. But since Atlas is intended to support both the client and server centric programming models, any thoughts of formally supporting the history stuff through the client-side at some point? Last I heard, Script# is a cool but unofficial project with a totally unknown future, so it's hard to consider doing much serious with it at this point.

Also, is the intent to roll the history stuff into the base framework at some point? It certainly would be nice to have it there.

Shahar Nechmad

Posted on 9/12/2006 @ 12:47 AM
Very cool! I'm sure going to try this. Back button problem could get very serious in some scenarios.
I have a related question about using Update Panels. Is there any way to actually catch events of the Update Panel on the client side? For example - I would love to change the cursor of the mouse when an update is starting and change it back when the it finished (This is just a simple example, I can think of many other scenarios why I will want something like this. Actually it could also be usefull to your prototype control. You will be bale to add history simply when an update has finished).

Nikhil Kothari

Posted on 9/12/2006 @ 6:18 AM
Shahar, in a future version of UpdatePanel, there will be events on the client-side like you're asking for.

Keep in mind, you wouldn't want to add a history event for each update - that would defeat the purpose... which is to only add history entries for logical entries... and not for every postback (the behavior you have today with regular post-backs is also suboptimal, and you wouldn't want to go back to that state).

Sunny Nagi

Posted on 9/12/2006 @ 6:09 PM
Excellent stuff Nikihil..!!

I tried in Firefox but didn't work for me, however it works fine in IE.

Sunny Nagi

Posted on 9/12/2006 @ 6:12 PM
My bad...

It worked just had to clear cache on my machine..

Kevin Hoang Le

Posted on 9/12/2006 @ 9:23 PM
Cool. Do you have a live demo where I can check it out ? I also developed a solution to the browsers' back button for Ajax apps. My solution is built on the prototype library, so it makes sense where prototype is central to your Ajax app. You can read it here http://pragmaticobjects.org/index.php?itemid=15 for a manual on how to use it or check out a live demo http://pragmaticobjects.org/ajaxPetGallery. From the demo, one can see that there's support not just for the back button but also forward button.

The demo also degrades gracefully for Javascript-disabled browsers. So, in both Ajax and non-Ajax modes, the demo behaves identically with regards to back/forward buttons.

Nikhil Kothari

Posted on 9/12/2006 @ 10:59 PM
Kevin, thanks for sharing - from the looks of the API you have, we're both building a similar model, though I haven't looked at your implementation yet to see if the similarity extends beyond public APIs.

As mentioned in the post, there is support for forward button as well - back and forward really aren't that different for that matter when it comes to history management. Adding graceful degrade is relatively straightforward - I didn't do it for the prototype, but it involves adding a browser check plus some conditional blocks.

Sorry, I don't have a live demo yet to point you at right now...

Ben

Posted on 9/13/2006 @ 7:31 PM
Not exactly on topic, but wasn't sure where else to ask.

I'm playing around with building an Atlas-based chat thing, just to get familiar with Atlas.

I've got a multiline textbox, called txtMessages, inside an update panel. The trigger is an Atlas timer that ticks every three seconds. When the panel posts back, it checks for any new entries in the database and displays them in the text box.

Outside the update panel I have a singleline textbox, txtChat and a button, btnChat, to click which writes the contents of the singleline textbox to the database.

My problem is that if you are typing in txtChat and the updatepanel refreshes, txtChat loses it's focus.

Any ideas on how to preserve the focus when the updatepanel updates? I've tried putting txtChat and btnChat in their own updatepanel, but it had no effect.

Thanks.

Kasumi38

Posted on 9/18/2006 @ 11:14 AM
Dear Nikhil,

When I downloaded the sample Zip file, unziped the file to localhost wwwroot folder and typed the address in IE like "http://localhost/HistoryDemo/History.aspx" to run the application, I still saw the postback action in the web page. Did I miss something like Script# compiler? Please tell me how to run the application smoothly!
Thanks
Kasumi38

Nikhil Kothari

Posted on 9/18/2006 @ 11:28 AM
Kasumi - based on your setup description, I think you need to mark the HistoryDemo directory as an app in IIS.
However, the simplest way to try it out is to use Visual Web Developer or Visual Studio, and choose File | Open | Web Site... and pick the HistoryDemo directory. You don't need to change any IIS configuration etc. You should be able to just hit F5 at that point...

Bob

Posted on 9/19/2006 @ 4:30 AM
Hi there,

I'm ( relatively new to ASP.NET, ) trying to use this and have the following problems/behaviour ;

If I open History.aspx in the IDE it looks ok - can see control etc.

However, if I add
<%@ Register TagPrefix="nk" Namespace="nStuff.Samples.AtlasControls"%> and
<nk:HistoryControl runat="server" ID="history" OnNavigate="OnHistoryNavigate" /> to my page and
HistoryControl.cs and History.js to my project..
I get "Error creating control unknown server tag nk:HistoryControl" ( in designer/IDE ).

( I tried it without register at first )

I've tried several approaches and trawled the net / msdn but can find no useful info - nearest doc relates to .UserControls

I notice the AtlasControlToolkit.dll in the zip file is a different size/timestamp from the one I downloaded a couple of days ago ( from ms ). Also HistoryControl.cs contains ;
using Microsoft.Web.UI;
using Microsoft.Web.Script;

Microsoft.Web is listed as "not currently supported" on msdn ( http://msdn.microsoft.com/library/default.asp?url=/workshop/webcontrols/assemblies/webcontrols_entry.asp ) and I cannot figure out how to add a reference in order to build with HistoryControl.cs.

So, how do I get the code working in an existing web project ?

Nikhil Kothari

Posted on 9/19/2006 @ 10:53 PM
Bob, the sample doesn't use the Atlas control toolkit - it uses just Atlas. Make sure you've downloaded Atlas, and included the Atlas assembly in the application's bin directory. The atlas assembly is the one that defines those namespaces you list.

Bob

Posted on 9/20/2006 @ 8:03 AM
Yeah, copied the wrong thing into the post ; should've said Microsoft.Web.Atlas.dll file differs from current ms download ( size and timstamp ). Not that it was relevant anyway as..

found the "Walkthrough: Developing and Using a Custom Server Control" article and.... used App_code dir with HistoryControl.cs as directed - which got me up and running...but only for debug/dev so.....( article uses command line for next bit )

added new class library project, added HistoryControl.cs to that, added refs to Atlas and System.web , built that and added ref to soln, removed previous history related code from my pages and removed App_code dir, added History control ( from new assembly ) to toolbox, dropped it on page and all done.

Crucial part was knowing to include ref to Atlas dll in new class library.

Thanks.

James Chambers

Posted on 9/20/2006 @ 8:54 AM
Hello, Nikhil,

I just recently attended Development Night in Canada (hosted by the Winnipeg .NET user group and MSDN Canada) and this control addresses the largest concern that was raised at the event.

Interestingly, most developers I spoke with afterward seemed to indicate that although this drawback was of great enough concern to address, it wouldn't stop them from using Atlas for the time being.

Two great things from this post: one, you've adressed the concern (albeit in an admittedly tempory fasion) about the back button, and, two, you've indicated that changes coming in the toolkit will allow for advancement and tighter integration and, thus, less 'hacks'.

With the event paradigm being added to the client-side of things I'm sure we'll see even more interesting things open up.

Although I understand that all this could be developed outside of the toolkit (and I've developed similar functionality to some components of Atlas), what I've found in my experience with other developers is that this development isn't the kind of thing that they're paid for. Though many of us would like to incorporate such functionality into our web apps, we pretty much have to wait for the toolkit (where the bits are already prepared) before we can jump on it.

Cheers,
James Chambers

James Chambers

Posted on 9/20/2006 @ 8:56 AM
Lol...you've probably been asked this, but why don't you use an UpdatePanel for the CAPTCHA at the bottom (or the new CAPTCHA control in the toolkit, if you're into dogfooding ;o)?

-jc

Nikhil Kothari

Posted on 9/21/2006 @ 9:21 AM
James, on your comment on why I don't use UpdatePanels on this site - its simply a resource constraint. Most of my time goes into creating future technology at work, or my various projects in my spare time - the site is first a means to an end... that said, I do have a number of plans and ideas for using ajax to improve a blog site like mine, but alas, its often the case that something else trumps that work in the stack ranking exercise. Folks from my team know how many times I've applied the argument "I would want to do this on my site..."

On your previous comment, there are definitely more developers who cannot (rightfully so) afford to or don't want to be on the bleeding edge. There are some that can... one of my goals here is to gather the feedback from folks who do have the luxury to delve into these new, sometimes half-baked things such that it can be incorporated into the real thing.

David V

Posted on 9/27/2006 @ 11:52 AM
Thanks Nikhil! We have been waiting for something just like this. Is there any chance that this control will make it into the official ASP.NET AJAX Extensions release (with full Safari support)? Dealing with the back button is such a fundamental aspect of AJAX style programming that the release would seem less than complete without it.

Dattatray Gaonkar

Posted on 10/25/2006 @ 10:52 PM
thnkx

Steve Roberts

Posted on 10/26/2006 @ 7:51 AM
Hi Nikhil,

I was wondering if you know if your history control will work with the latest changes made to the ASP.NET Ajax version of the UpdatePanel? (or if it is actually still needed?)

- basically I'd implemented a page using your control and it was working splendidly, but I've just updated from Atlas to Ajax and my back button no longer works.

Thanks,
Steve

Michael Sawczyn

Posted on 10/27/2006 @ 8:29 PM
Nikhil,

Any thoughts of updating this now that Atlas is "official"? Looks like some changes are needed to match the new architecture rules, but I don't have enough understanding of the ScriptManager framework to do these myself.

Excellent work, by the way. A great solution to a big problem.

-- Michael

John

Posted on 11/1/2006 @ 8:25 AM
Nikhil,

I would be very interested in an updated version of this control. I have had a look myself but I don't understand the client script enuff to beable to update it myself.

Mariano

Posted on 11/9/2006 @ 1:59 AM
Is there any chance you could update your control to work with AJAX Beta 2?
The code currently breaks at scriptManager.RegisterScriptReference(ResolveClientUrl("~/ScriptLibrary/History.js")); because that method doesn't exist anymore.

Nikhil Kothari

Posted on 11/11/2006 @ 1:34 PM
Most comments boil down to request for a new version. There will be a new version that works with the released version of ASP.NET AJAX soon. Stay tuned...

Dario Renzulli

Posted on 11/21/2006 @ 2:55 AM
I made some changes for the new version and it's working. This is the list:
1) Add new assembly to bin folder.

2) Change in the web.config the assembly references

3) Modify aspx page:
3 a) "mode" attribute is not longer supported, change to "UpdateMode"
3 b) Change "ControlEventTrigger" to "AsyncPostBackTrigger"

4) In App_Code\HistoryControl, remove:

scriptManager.RegisterScriptReference(ResolveClientUrl("~/ScriptLibrary/History.js"));

and write this:

ScriptReference scriptReference = new ScriptReference();
scriptReference.Name = "History.js";
scriptReference.Path = ResolveClientUrl("~/ScriptLibrary/History.js");
scriptManager.ScriptMode = ScriptMode.Auto;
scriptManager.Scripts.Add(scriptReference);

And voila!!!

Test it and tell me...

my two cents...

Dario.

Erik

Posted on 11/22/2006 @ 4:35 AM
I have tested your new code. It works fine in mozilla but not in IE7. Can you please test it in IE7. I get some javascript errors.
window.historyTracker in null or not an object
Sys is undefined

Thanks for your great work!

Dario Renzulli

Posted on 11/22/2006 @ 5:04 AM
My modification is useless...
It doesn't work really, i realised that the page is doing a postback :-(
sorry ....

Darío.
The discussion on this post has been closed. Please use my contact form to provide comments.