Fact: The web is stateless.

The World Wide Web, on the other hand, is intrinsically stateless because each request for a new Web page is processed without any knowledge of previous pages requested. This is one of the chief drawbacks to the HTTP protocol. Because maintaining state is extremely useful, programmers have developed a number of techniques to add state to the World Wide Web. These include server APIs, such as NSAPI and ISAPI, and the use of cookies.

Source: Webopedia

As "maintaining state is extremely useful", there are plenty of ways to maintain the state across requests.

An introduction to states in a web environment

However, you have to be careful how you do it because there are many ways that a request can be done. The most common are:

  • A direct GET request through a bookmark or an URL entered directly in the address bar
  • A GET request coming from an external web site (this is essentially the same as a direct request in terms of state: there's no previous state)
  • A GET request coming from an internal page of your web site
  • A POST request. These usually come from an internal page of your web site, but you can't count on it

In the case of a GET request coming from an internal page, there's also the possibility that a new tab was requested, making the originating tab a valid state. If you've ever seen a revision graph (I sure hope you have), a state graph would be quite similar.

State Graph

State Graph

The merging is not exactly present as in the revision graph, but it's there. In fact, an opened tab/window could have an effect on any other tab, not only its parent. Let's say the user opened a tab from a listing of items to edit a particular item. It modifies the item and saves it. The next state in the listing would reflect (ideally) the modifications to the item.

Maintaining the state

Now that you are more familiar with the state in a web environment, let's get to the point: how to persist information across requests. From the discussion above, we can extract two key points:

  • Never assume the request comes from an existing state, always check if a state existed before
  • Never modify a state directly, always make a copy (in case of tabs)

Most web frameworks (if not all) offer one or more ways of maintaining the state. Most of them rely on the good old cookies, but you have to be careful with those because you don't want to change an existing valid state.

You could also use a session identifier, but you can't always rely on them because different browsers treat sessions differently. Firefox and IE both consider that each tab is in the same session as the originating tab while Chrome considers them differently, it treats each tab as a new window (which is a different session for every browser).

Another (very ugly) way, is to simply pass an identifier (usually a Guid) in the URL all the time. However, you can't assume that the user just won't delete the ?SessionID=... part from your URL before performing a request.

Finally, the Asp.Net WebForms framework simulates a stateful application by wrapping everything in a single form field and doing everything by using post backs (POST requests). A post back does not allow new tabs so it's safe to just change the originating state.  It also makes something clear, every GET request is considered a new entry point and does not rely on the previous state to perform the actions.

Cleaning unused states

Persisting all those states can make a dent on your memory usage, that's why you'd want to remove unused (closed tabs/windows) states from the graph. To do so, I suggest that you implement some sort of keep-alive object that sends Ajax request to keep the state of the page alive every x seconds. Once the requests stop coming, you can safely delete a state*.

* I haven't explored the possibility of resurrecting a tab (via undo close tab), this might be an issue and a reason to never delete a state until the user session is dead.

Conclusion

Stateless programming is not a problem easily solved and thoughtful thinking is required before using any of the solutions presented above.