author/Dino Esposito  from/dotnetslackers.com

Dino Esposito continues his examination of the ASP.NET AJAX History control.

Introduction
By design, AJAX applications tend to do most of their tasks within the same page without changing the URL. This makes it quite complicated for a browser to track any distinct states the application transits. History points are an attempt to add history support to ASP.NET AJAX applications. In this article, I'll examine the client-side API in ASP.NET 3.5 Extensions.

History Points
A "history point" is a logical step in an ASP.NET AJAX application that you want to be able to return to at some point in the application lifetime. You create a "history point" by associating some state information with the current page URL. The ASP.NET AJAX runtime then manages to save a mangled URL to the browser's cache of Back/Forward navigation points so that those application-specific history points are displayed to the users in the menu of recently visited URLs.

From a programmer's standpoint, you create a history point by using some new methods defined in the ScriptManager class of ASP.NET 3.5 Extensions. You may invoke this extended API from many places, but typically you create a history point in an event handler when you detect that you are in a state you want to return later - for example, page 4 of a pageable grid.

In a previous article on DotNetSlackers, I covered the server-side API for adding history points. Basically, in that article I discussed the methods and techniques you need to use to save a history point in the handling of a server event. But a similar API also exists for the client-side events that you handle using JavaScript code.

Enabling History Management
To enable history management, be it for server-side or client-side programming, you need to set the EnableHistory property to true on the ScriptManager control. If you're in the context of a master page, then you might want to use ScriptManagerProxy instead. This setting enables the download on the client of proper JavaScript code that contains the client-side classes for history management.

<asp:ScriptManager runat="server" ID="ScriptManager1" EnableHistory="true" /> 
<asp:ScriptManager runat="server" ID="ScriptManager1" EnableHistory="true" />

It should be noted that a trick is required to add the history point to the browser's list of recently visited URLs. No browsers, in fact, do offer a programmatic API to add new URLs to the list. The Microsoft script code performs browser-specific tricks that basically consist of forcing the browser to navigate to the mangled URL in a way that doesn't affect the user. For Internet Explorer, in particular, the trick consists in the creation of an invisible IFRAME that downloads the mangled URL, thus adding it automatically to the list of visited URLs.

Creating Client History Points
You add a client history point in a JavaScript function associated with a user event - for example, a button click. Imagine an ASP.NET AJAX page that invokes a remote Web service to grab information about a customer.

function findCustomer()   

    var list = $get("CustomerList"); 
    var customerID = list.options[list.selectedIndex].value; 
    lookup(customerID); 

function lookup(customerID) 

    Samples.WebServices.MyDataService.LookupCustomer( 
        customerID, 
        onSearchComplete); 
         
    Sys.Application.addHistoryPoint( 
        {s: customerID},   
        "Customer:" + customerID); 

function findCustomer()
{
    var list = $get("CustomerList");
    var customerID = list.options[list.selectedIndex].value;
    lookup(customerID);
}
function lookup(customerID)
{
    Samples.WebServices.MyDataService.LookupCustomer(
        customerID,
        onSearchComplete);
       
    Sys.Application.addHistoryPoint(
        {s: customerID},
        "Customer:" + customerID);
}

In the sample code, you select an item from a drop-down list and pass it to a JavaScript helper that invokes a remote Web service. Immediately after firing the call, you save the history point. You use the Sys.Application.addHistoryPoint method to define the history point. The method allows you to define the URL as well as the title to appear in the browser list. The URL is mangled with page-specific state information that will be passed back to your code when the user navigates back to the URL from the browser's history.

In general, state information is a collection of key/value pairs. In this case, the state is a single piece of data - the customer ID. The preceding code snippet produces the graphical effect depicted in Figure 1.

Figure 1: Custom titles in the browser’s history list
Client History Points in ASP.NET 3.5 Extensions_2695

Each history point is characterized by a modified URL and a title in the browser's history list. The URL is modified as below:

page.aspx#s:ALFKI 
page.aspx#s:ALFKI

It is worth noting that what follows the # symbol is the serialization of the state information you provided when you created the history point. You can use the following code to create a permanent link to the state:

var permalink = "#" + Sys.Application.get_history().get_stateString(); 
var permalink = "#" + Sys.Application.get_history().get_stateString();

Now that you know how to save history points, it is about time to figure out how to restore a previously reached state.

Restoring a State from a History Point
The Sys.Application JavaScript class kicks in whenever the page being displayed has a URL whose pattern matches the mangled URL of history points. When such a page is navigated by the user, the navigate event is fired to the client code. You need to add a handler for this event. A possible place where you can do this is the pageLoad function - an ASP.NET AJAX function that, if present, executes when the page's client-side object model has been fully initialized.

function pageLoad(sender, args) 

    Sys.Application.add_navigate(onNavigate); 

function onNavigate(sender, e)   

    var customerID = e.get_state().s; 
    restoreState(customerID); 
}   
function pageLoad(sender, args)
{
    Sys.Application.add_navigate(onNavigate);
}
function onNavigate(sender, e)
{
    var customerID = e.get_state().s;
    restoreState(customerID);
}

In the handler for the Sys.Application.navigate event, you first retrieve the saved state string and then restore the proper application's state - whatever that means for the current page. For this service, you can't rely on any predefined browser or library behavior. In the previous code snippet, all this logic is contained in the restorePage helper function.

It is worth noting that the navigate event is also fired when you add a history point and not just when a URL that contains history state is navigated by the user. This may pose an issue if you need to perform potentially heavy operations to restore the state. Consider the previous example.

In the event handler you make a roundtrip to the server to download customer information. Next, when a previously saved page is navigated again, you restore the state. To restore customer information, though, you reasonably need to repeat the remote access. This means that, unless you take countermeasures, you make a first roundtrip when you add a history point and repeat it immediately after when the navigate event is fired. This is not an issue if you use cached data to restore the state, but it is a point to be kept in mind just in case.

function restoreState(customerID) 

    if (customerID == undefined) 
        clearUI(); 
    else 
        lookup(customerID); 

function restoreState(customerID)
{
    if (customerID == undefined)
        clearUI();
    else
        lookup(customerID);
}

You can use a Boolean global variable to track whether the navigate event is fired from within the context of a call to addHistoryPoint.

var userNavigated = true; 
 
function lookup(customerID) 

    Samples.WebServices.MyDataService.LookupCustomer( 
        customerID, 
        onSearchComplete); 
         
    userNavigated = false; 
    Sys.Application.addHistoryPoint( 
        {s: customerID},   
        "Customer:" + customerID); 
    userNavigated = true; 

function onNavigate(sender, e)   

    var customerID = e.get_state().s; 
    if (userNavigated) 
        restoreState(customerID); 

var userNavigated = true;

function lookup(customerID)
{
    Samples.WebServices.MyDataService.LookupCustomer(
        customerID,
        onSearchComplete);
       
    userNavigated = false;
    Sys.Application.addHistoryPoint(
        {s: customerID},
        "Customer:" + customerID);
    userNavigated = true;
}
function onNavigate(sender, e)
{
    var customerID = e.get_state().s;
    if (userNavigated)
        restoreState(customerID);
}

You set the Boolean variable just before you add the history point. In this way, it is set when the navigate event subsequent to marking the history is fired. As a result, no state is restored. Immediately after adding the history point, you reset the variable.

Next, when the user navigates to the history point, the variable is not set and the state associated with the history point is correctly restored.

The navigate Event
The navigate event requires a handler with the following prototype.

function onNavigate(sender, e)   
function onNavigate(sender, e)

The sender argument is the Sys.Application object of the current page. The e argument evaluates to an instance of the HistoryEventArgs class defined in the version of the Microsoft AJAX library that ships with ASP.NET 3.5 Extensions. The HistoryEventArgs class features one key property - state.

Just as any property defined on classes in the Microsoft AJAX library, the state property is accessed using a get method accessor - get_state. The state property returns a name/value collection that represents the state. In this example, I used a collection with just one entry, but you're allowed to use as many entries as required by the logic you intend to implement.

Note: The navigate event is also fired if a portion of the page is involved in a partial refresh conducted through an UpdatePanel control.

Summary
History points are a key enhancement for AJAX applications. They keep AJAX pages usable through Back and Forward browser buttons and also help the page to be ranked by search engines. ASP.NET 3.5 Extensions develops a programming model that is nearly the same on both the client and the server meaning that you can use this model with both managed languages such as C# and JavaScript.

All that you have to do is invoking a method that adds a history point when in your client or server code you find a state that you want to be able to return. You provide a name/value collection of information that identify that state uniquely and, at the same time, is informative enough to let you run code to restore the state entirely. A mangled URL is added to the browser's navigation stack and whenever the user navigates to such a URL an event is fired to your server and client code. The event carries the same state information you previously saved and gives you a chance to restore the desired state.
回复 TOP