ASP.NET Ajax History

As seen in the Crumb Trail Data section, StateContext Data should be populated with the data required to recreate the current State. This is so that when it is Navigated back to via a Crumb all this data will be passed in and the screen can be presented to the user as it was the last time they visited it, e.g., in the Data Sorting and Paging section, storing the sort order in StateContext Data means when they Navigate back the sort order is retained. The image below illustrates the way StateContext Data can be updated as PostBacks occur and since StateContext Data is stored in ControlState the browser back button causes no problems.
Browser Back and StateContext
Browser Back and StateContext

ASP.NET Ajax and the UpdatePanel automatically add Ajax functionality to any Page, converting traditional PostBacks into PartialPageRequests. Consequently the browser back button no longer inserts history in the ‘usual’ manner. To circumvent this, the ScriptManager Control exposes the AddHistoryPoint method which allows the programmatic addition of history to the back button. This method takes a parameter which should contain all the data needed to recreate the Page. When the back button is pressed the ScriptManager raises an event passing back this data so the Page can be recreated. So, passing the StateContext Data to the AddHistoryPoint method means the image above remains the same except PostBacks are now replaced with partial PartialPageRequests and Browser Backs with ScriptManager events.

The StateController class has an AddHistoryPoint method that calls the ScriptManager AddHistoryPoint method passing in NavigationData; and a NavigateHistory method that sets back this NavigationData into StateContext Data as a result of a ScriptManager history event. The latter is considered as history Navigation. A history Navigation does not result in a change to the PreviousState stored in StateContext as this is only changed during a non-PostBack scenario, i.e., forward, backward and refresh Navigation.

Be aware of the following:
  1. History Navigation is not available in VS 2005 since ASP.NET Ajax AddHistoryPoint is not available
  2. In VS 2008 ASP.NET Ajax AddHistoryPoint functionality throws errors if reserved HTML/URL characters are contained in the state data. To avoid this set EnableSecureHistoryState on the ScriptManager to true
  3. History Navigation does not work in Opera prior to version 11 since ASP.NET Ajax AddHistoryPoint produces very strange query strings in this browser
Rather than performing history Navigation programmatically, there is a HistoryNavigator control that, by detecting changes to StateContext Data, handles it automatically. This control should be added towards the bottom of the Page, after any data bound controls, because the change detection runs in the PreRender event and expects all StateContext Data updates to have taken place by then.

The HistoryNavigator allows customisation of when history points are added. By default a history point is added whenever a change is detected to any StateContext Data item. By populating HistoryNavigator's HistoryKeys property with a comma delimited list of keys the change detection will only run against this subset of StateContext Data.

ASP.NET Ajax manages history by storing state in the hash fragment of a URL, following the && separator, for example:

Default.aspx#&&key=value
Prior to HTML5 modifying the hash fragment was the only option. However, the advent of HTML5 introduced the History API which allows custom URLs to be used, eradicating the hash fragment for modern browsers.

Although ASP.NET has not updated its Ajax History implementation to use HTML5 History, support has been added directly into the Navigation framework. To make use of this add the ScriptReference below to the ScriptManager's Scripts list.

<asp:ScriptReference Name="Navigation.HTML5History.js" Assembly="Navigation" />
If the browser does not support the new HTML5 History API it will fall back to the default ASP.NET Ajax History behavior. HTML5 History support will really pay dividends in the ASP.NET Routing section because it will mean all URLs, including history, can be made user-friendly. It also results in faster loading of bookmarked links because there is no longer the delay of waiting for the javascript to run.

There are a few provisos to be aware of when adding this ScriptReference:
  1. History points must be added using the StateController
  2. The page must not have been navigated to using NavigationMode.Server
  3. Cross page posting is not supported from a page with route parameters

Sample Web Site

Start by enabling ASP.NET Ajax History on the ScriptManager, ensuring Secure History is disabled, as shown below.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="True" EnableSecureHistoryState="false" />

To enable the browser back button the AddHistoryPoint method on the StateController class must be called. Add a handler to the GridView’s DataBound event and call the AddHistoryPoint method passing a copy of the current StateContext Data (see below). This DataBound method is ideal as it is only called when the NavigationDataParameters change, i.e., when the StateContext Data changes.

protected void GridView1_DataBound(object sender, EventArgs e)
{
	if (!ScriptManager1.IsInAsyncPostBack || ScriptManager1.IsNavigating)
		return;
	StateController.AddHistoryPoint(Page, new NavigationData(true), null);
}
Now each time the Person List is (validly) filtered, sorted or paged the browser back button gets a new history page added. Pressing the Search Button multiple times without changing the search criteria does not add a history point. This is because it is only on a change of NavigationDataParameter that the GridView gets DataBound. However, the browser back button does not ‘work’ as the search criteria and the Person List are not restored.

To restore the Person List, add a handler to the ScriptManager’s Navigate event and call the NavigateHistory method on StateController as shown below.

protected void ScriptManager1_Navigate(object sender, HistoryEventArgs e)
{
	if (ScriptManager1.IsInAsyncPostBack)
		StateController.NavigateHistory(e.State);
}
Press F5 and filter, page and sort the Person List a few times. Pressing the back button successfully cycles the Person List back through its previous incarnations. This is because the NavigateHistory replaces the StateContext Data with that associated with the HistoryPoint, which causes the GridView to rebind.

To restore the search criteria, the FormView must be rebound. This can be done by adding name and minDateOfBirth SelectParameters to the NavigationDataSource as shown below. This causes the Text Boxes to rebind their Text properties against their associated StateContext Data whenever a change is detected. Now pressing the back button cycles the Text Boxes back through their previous values.

<cc1:NavigationDataSource ID="NavigationDataSource1" runat="server">
	<SelectParameters>
		<cc1:NavigationDataParameter Name="name"/> 
		<cc1:NavigationDataParameter Name="minDateOfBirth"/> 
	</SelectParameters>
	<UpdateParameters>
		<cc1:NavigationDataParameter Name="sortExpression" Reset="true"/>
		<cc1:NavigationDataParameter Name="startRowIndex" Reset="true"/>
	</UpdateParameters>
</cc1:NavigationDataSource>
An alternative approach to history management is to use the HistoryNavigator control. So, delete the DataBound and Navigate event listeners from the codebehind and their corresponding attributes in the Design view; and instead add a HistoryNavigator after the Pager control, as shown below.

<cc1:HistoryNavigator runat="server" />
Press F5 to see the history working as before, but now without any codebehind.

Now that history Navigation is working as desired, the final stage is to get it to use HTML5 History. By adding the ScriptReference shown below, the history URLs no longer use the hash fragment. In fact, they're identical to the URLs produced when javascript is disabled.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="True" EnableSecureHistoryState="false">
	<Scripts>
		<asp:ScriptReference Name="Navigation.HTML5History.js" Assembly="Navigation" />
	</Scripts>
</asp:ScriptManager>

Last edited Aug 4, 2013 at 6:31 PM by GrahamMendick, version 6