Wednesday, October 29, 2008 5:08 PM
An inherent problem with AJAX-style applications is browser navigation. AJAX pages don’t post back when performing requests tasks so they can’t remember their “previous states”. The problem comes when users click the browser's back button. What happens is browsers don’t return to a previous “state” of the page, instead, the browser unloads the page entirely and returns to the previous page. Technically speaking, this is OK because one may argue it’s just how AJAX works. However, a more user-friendly design needs to be able tackle this problem.
The ASP.NET AJAX Framework came with native support for back button and history management back in July 2007, when it was still called ASP.NET Futures. You would have to use a History control back then to achieve this goal. In the latest ASP.NET 3.5 release, the History control was removed from the framework, and all its functionality was ported into ScriptManager. Adding back button support to your AJAX page has become easier than ever. In today’s post, we will be looking at a typical “back button”: navigate in AJAX page with tabbed content. And then we will solve the problem by introducing the back button support.
Let’s start with creating a new ASP.NET web site in Visual Studio 2008. I named the web site TabbedContent. Go ahead and add a ScriptManager and a UpdatePanel to the default.aspx form. This will enable AJAX in our site. Now drop a TabContainer control inside the UpdatePanel. Note the TabContainer control was shipped with AJAX Control Toolkit. If you don’t have the toolkit installed, you can download it from CodePlex. Once you have placed the TabContainer in the UpdatePanel, you can add a few TabPanel controls to it, and of course you can throw any content onto each TabPanel at your choice. If you want, you can copy and paste what I have in the TabContainer. I created three tabs, each for an auto maker, listing the models in a BulletList control.
<cc1:TabContainer ID="TabContainer1" runat="server" ActiveTabIndex="0">
<cc1:TabPanel ID="tpAudi" runat="server" HeaderText="Audi">
<ContentTemplate>
<asp:BulletedList runat="server">
<asp:ListItem>A4</asp:ListItem>
<asp:ListItem>A5</asp:ListItem>
<asp:ListItem>A6</asp:ListItem>
<asp:ListItem>A8</asp:ListItem>
</asp:BulletedList>
</ContentTemplate>
</cc1:TabPanel>
<cc1:TabPanel ID="tpBMW" runat="server" HeaderText="BMW">
<ContentTemplate>
<asp:BulletedList ID="BulletedList1" runat="server">
<asp:ListItem>335</asp:ListItem>
<asp:ListItem>528</asp:ListItem>
<asp:ListItem>745</asp:ListItem>
<asp:ListItem>Z4</asp:ListItem>
</asp:BulletedList>
</ContentTemplate>
</cc1:TabPanel>
<cc1:TabPanel ID="tpMercedez" runat="server" HeaderText="Mercedez">
<ContentTemplate>
<asp:BulletedList ID="BulletedList2" runat="server">
<asp:ListItem>CLK 500</asp:ListItem>
<asp:ListItem>S 550</asp:ListItem>
<asp:ListItem>E 350</asp:ListItem>
<asp:ListItem>C 60 AMG</asp:ListItem>
</asp:BulletedList>
</ContentTemplate>
</cc1:TabPanel>
</cc1:TabContainer>
Now your web site should be complete.Go ahead and run it.
As you can see, the AJAX page doesn't post back when I switch between the tabs. The back button is always disabled because everything stays on the same page. So you may be wondering what the problem is. Everything happens on one page with no page postbacks. Isn’t that great user experience? Well yes and no. Let’s introduce the problem by adding another page login.aspx. This page has nothing on it but a redirect link to default.aspx.
<form id="form1" runat="server">
<div>
<asp:HyperLink ID="hlDefault" runat="server" NavigateUrl="~/Default.aspx" Text="Go to default" />
</div>
</form>
Now set the login.aspx as the startup page and run the web site again. Click on “Go to default”. Note the browser’s back button becomes enabled when you get redirected to default.aspx from login.aspx.
On the default.aspx page, switch to Mercedez from Audi, and then click the back button. Oops… what happened? Now you are on the login page again. Didn’t you want to go back to Audi from Mercedez? Imagine how annoying this can be for a car buyer who is comparing different brands.
Now we are going to solve this problem by leveraging the new history feature in ScriptManager. The key thing here is have the browser remember the history points as we navigate in an AJAX page.
Step 1 – Enable history point in ScriptManager.
This is a new feature only available in ASP.NET 3.5 and later. It’s pretty simple. You just need to add EnableHistory=”true” to the ScriptManager on your page.
<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="true"
/>
Step 2 – Store the current ActiveTabIndex property of the TabContainer control in ViewState. We will be using the stored value in a moment.
Place this code in your default.aspx code behind file.
protected int ActiveTabIndex
{
get
{
return ViewState["ActiveTabIndex"] != null ? Convert.ToInt32(ViewState["ActiveTabIndex"]) : 0;
}
set
{
ViewState["ActiveTabIndex"] = value;
}
}
Assign the initial value to ActiveTabIndex when default.aspx first loads.
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
ActiveTabIndex = 0;
}
}
Step 3 – Wire up the TabContainer’s ActiveTabChanged event
protected void TabContainer1_ActiveTabChanged(object sender, EventArgs e)
{
ActiveTabIndex = TabContainer1.ActiveTabIndex; // Update the ActiveTabIndex property
ScriptManager1.AddHistoryPoint("ActiveTabIndex", ActiveTabIndex.ToString());
}
Every time the tab index is changed, we need to make sure the ScriptManager remembers the last “state” of the TabContainer. It’s important to set the AutoPostback property to true in order for the ActiveTabChanged event to be fired.
Step 4 – Wire up the ScriptManager’s Navigate event.
This event is raised during asynchronous postbacks automatically when the server-side history state changes.
<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="true"
onnavigate="ScriptManager1_Navigate" />
Our server-side code for navigation is very straightforward. All the history points were already stored during postbacks. Now we just need to retrieve the value and assign it to the TabContainer’s ActiveTabIndex property. The history points are represented by a NameValueCollection.
protected void ScriptManager1_Navigate(object sender, HistoryEventArgs e)
{
TabContainer1.ActiveTabIndex = Convert.ToInt32(e.State["ActiveTabIndex"]);
}
With all the changes we have just made, we should be able to switch between the tabs by clicking on the back button. This is illustrated by the screen shots below.


Finally, here’s the source code of the demonstration. Feel free to download it.