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.
Posted on Sunday, 9/10/2006 @ 6:28 PM
| #
ASP.NET