A brief history of History
ASP.NET has introduced a very powerful component model for the Web at a time when templating engines were limited to inserting code into markup placeholders. The new level of abstraction aims at reproducing the component and event-based model that was familiar to VB developers on the Web, along with the productivity boost it represented. At the time, Ajax was a concept that was almost limited to a few big applications such as Outlook Web Access and that didn't even have a clearly defined name. Most of the interactivity was implemented on the server-side, which means that a round-trip to the server was necessary for each and every user interaction. This, in addition to the disconnected nature of HTTP and the scalability requirements of Web applications, set state management as one of the most important problems that a Web framework had to solve. It also meant that a lot of redundant data was travelling back and forth as the server had to reconstruct the application's state on the one side, and the browser had to reconstruct the page UI on the other, every single time the user was doing anything. One advantage of this system is that every user interaction resulted in the browser creating a point in History and adding it to its back button's stack of previous states, making it possible and relatively natural for the user to step back. If anything, there was an excess of history points.
It was only a matter of time before the industry moved to a more rational model where the client-server conversation becomes a lot less chatty and redundant. This is essentially what Ajax is all about, but switching to a model where the page gets its updates out of band instead of posting back and rebuilding itself from scratch means that the browser has no clue that anything significant happened and no history points get created. Essentially, we went from too many history points to none at all!
In ASP.NET Ajax, you can go all the way and implement a lot of logic on the client-side, which involves some investment in the understanding of client-side technologies, or you can continue to implement the logic in server-side code using
UpdatePanels, or even have a mix of
UpdatePanels and client-side code. In all three cases, ASP.NET provides a simple and efficient way of restoring the back button. What's interesting is that it is now up to the application developer to decide which user interactions represent a change that should be reflected by a point in browser history. In summary, we're now moving from no history points to the exact right number of history points.
An important side-effect of implementing browser history in an Ajax application is that each state of the application becomes bookmarkable.
How to prepare an application for history
To make a page work well with the back button, you'll need to determine the minimum set of variables that is necessary to represent its state. For a mapping application, this may be the current longitude and latitude and for a wizard it may be the current step index and maybe the contents of the different fields in each step. But one thing to keep in mind is that this set of variables needs to remain small because of the way history management systems have to be implemented. In effect the implementation is severely constrained by the fact that browsers don't have built-in APIs to manage the history stack as we speak (the next generation of browsers will very likely change that, seeing how important this scenario has become). The only place where the framework can store state is in the so-called fragment part of the url (the part after ‘#', which was originally designed to enable links within a single page). This is a very different process than the ViewState model where everything is automatically maintained (and as a consequence can become very large if one is not careful).
It is actually a very useful and structured design phase to stop and think about what constitutes the state of the application.
A simple Ajax wizard
Let's start with a simple wizard inside of an
The minimum state that we want to maintain here is the wizard's step index, and that's what we'll do here. Depending on what use we want to make of this page, we may also want to store the contents of each textbox, but these contents could potentially be quite large, so if it's not strictly necessary, I'd keep them out. An alternative for such potentially large pieces of state is to use a more traditional state store such as ViewState or Session, but of course you won't get history or bookmarkability on them.
Handling state changes and navigation
To handle history, once we've set
true, we only have two things left to do. The first one is to create history points every time state changes. In our case, this is done by handling the
ActiveStepChanged event of the wizard and creating a history point from there:
Listing 1: C# code
Listing 2: VB code
It is important to note that although we only have one piece of state in this sample (the wizard index), you can handle as many events as you want and independently create history points from there. Each part of the application can manage its own part of the state without having to know about the others. In other words, state is added by
AddHistoryPoint more than it is set, and setting a new entry doesn't delete the previously added ones.
The second thing we need to do is handle the
Navigate event on the
ScriptManager, which gets triggered every time the user clicked the back or forward button and restore the state from there:
Listing 3: C# code
Listing 4: VB code
You can think of these two things as analogous to
LoadViewState in the
An important thing to note here is that when restoring the state, our code has to account for empty state and restore the default state of the page in that case. This is to handle the case of the user going back from a later state of the page to the initial request to the page, where no state existed yet.
One could notice while running this application that the state on the URL looks very cryptic. Indeed, by default the server state is hashed using the same algorithm as
ViewState. This can be relaxed by setting
false on the
ScriptManager. In that case, the state becomes readable (and modifiable) and looks very much like a regular query string:
Listing 5: EnableStateHash = true
Listing 6: EnableStateHash = false
It's also important to say a word about security here. Even if the state is hashed, the data that the application puts in there is essentially user data, and there is potential for injection attacks. It is thus very important to validate that data before using it, as with any piece of user data.
Bertrand Le Roy is a program manager in the ASP.NET team.
This author has published 3 articles on DotNetSlackers. View other articles or the complete profile here.
You might also be interested in the following related blog posts
Adding users to a TFS project when youre not on the domain
Navigation for CoverFlow
Web Deployment Tool has gone RTW
An alternative to Crystal
Take Two: A jQuery WCF/ASMX ServiceProxy Client
Building Interactive User Interfaces with Microsoft ASP.NET AJAX: Triggering Full Page Postbacks From An UpdatePanel
Understanding Reverse Proxy Servers and the Mailman
Session State Not Working? Check Your Web Garden!
Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 6: Data Transfer Objects (DTOs)
AJAX Logging with Exponential Backoff
Please login to rate or to leave a comment.