1.Introduction

Vous êtes un développeur C#, vous travaillez avec ASP.NET et vous souhaitez pouvoir gérer l'historique de navigation et les bookmarks sur un site Ajax ?
Alors, ce tutoriel est pour vous.

A travers cet article, nous allons présenter comment utiliser le contrôle ScriptManager pour gérer un état et permettre ainsi la navigation entre points d'historiques Ajax ou simplement le bookmarking de certains états.
Tout au long de ce cours, je vais utiliser Visual C# 2008.

2.Le problème

Commençons par réaliser un petit menu tout simple où nous disposons d'un lien pour afficher la page Accueil et un autre pour afficher la page Contact.

Image non disponible

Pour ce faire, on va créer une simple page Menu.aspx avec deux LinkButton. Lors du click sur l'un d'eux, on affiche dans un PlaceHolder le contenu correspondant au menu choisi.

 
Sélectionnez

<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="Menu.aspx.cs" Inherits="DemoHistory.Menu" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

[...un peu de code en moins pour un peu plus de lisibilité...]

<body>
	<form id="form1" runat="server">
		<ul id="menu">
			<li>
				<asp:LinkButton ID="hpaccueil" 
					runat="server" OnClick="ClickLinkButton" Text="Accueil" />
			</li>
			<li>
				<asp:LinkButton ID="hpcontact" 
					runat="server" OnClick="ClickLinkButton" Text="Contact" />
			</li>
		</ul>
		<asp:Label runat="server" ID="labelTemoin" />
		<asp:PlaceHolder runat="server" ID="phaccueil" Visible="false">
			<asp:Label ID="Label1" runat="server" Text="Page d'accueil" /><br />
			<asp:Label runat="server" ID="dateAccueil" />
		</asp:PlaceHolder>
		<asp:PlaceHolder runat="server" ID="phcontact" Visible="false">
			<asp:Label ID="Label2" runat="server" Text="Page de contact" /><br />
			<asp:Label runat="server" ID="dateContact" />
		</asp:PlaceHolder>
	</form>
</body>

Le code behind associé renseigne l'heure courante dans le Label témoin et affiche/masque les PlaceHolder en fonction de l'élément du menu cliqué.
Notez que chaque PlaceHolder affiche simplement un message signalant quel lien a été cliqué et la date d'affichage.

 
Sélectionnez

public partial class Menu : Page
{
	protected override void OnLoad(EventArgs e)
	{
		labelTemoin.Text = DateTime.Now.ToLongTimeString();
		base.OnLoad(e);
	}

	protected void ClickLinkButton(object sender, EventArgs e)
	{
		phaccueil.Visible = false;
		phcontact.Visible = false;
		dateAccueil.Text = DateTime.Now.ToLongTimeString();
		dateContact.Text = DateTime.Now.ToLongTimeString();

		if (sender == hpaccueil)
			phaccueil.Visible = true;
		else
			phcontact.Visible = true;
	}
}				

Superbe menu n'est-ce pas ? Sauf qu'on aimerait bien que le chargement des PlaceHolder bénéficie d'un rendu partiel.
Très simple, rajoutons le fameux UpdatePanel et son acolyte le ScriptManager.

 
Sélectionnez

<form id="form1" runat="server">
	<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true" />
	<ul id="menu">
		<li>
			<asp:LinkButton ID="hpaccueil"
				runat="server" OnClick="ClickLinkButton" Text="Accueil" />
		</li>
		<li>
			<asp:LinkButton ID="hpcontact" 
				runat="server" OnClick="ClickLinkButton" Text="Contact" />
		</li>
	</ul>
	<asp:Label runat="server" ID="labelTemoin" />
	<br />
	<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Always">
		<ContentTemplate>
			<asp:PlaceHolder runat="server" ID="phaccueil" Visible="false">
				<asp:Label ID="Label1" runat="server" Text="Page d'accueil" /><br />
				<asp:Label runat="server" ID="dateAccueil" />
			</asp:PlaceHolder>
			<asp:PlaceHolder runat="server" ID="phcontact" Visible="false">
				<asp:Label ID="Label2" runat="server" Text="Page de contact" /><br />
				<asp:Label runat="server" ID="dateContact" />
			</asp:PlaceHolder>
		</ContentTemplate>
	</asp:UpdatePanel>
</form>

Aucun changement à faire dans le code behind, tout est automatiquement pris en charge par l'UpdatePanel.
Lors du click sur un menu, on constate le rechargement partiel, comme l'indiquent les heures différentes du Label témoin et du Label du PlaceHolder.

Image non disponible

2.3.Et le bouton "Précédent" de mon navigateur ?

Rajoutons une page Default.aspx à notre site comme point d'entrée de celui-ci. Cette page comportera un lien vers la page qui contient le menu, à savoir :

 
Sélectionnez
				
<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="Default.aspx.cs" Inherits="DemoHistory.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:HyperLink runat="server" Text="Aller à la page de menu" NavigateUrl="~/Menu.aspx" />
    </form>
</body>
</html>

Naviguons sur cette page, cliquons sur le lien. On arrive sur la page de Menu. On clique sur l'élément de menu "Accueil" qui nous affiche la page d'accueil. Puis, cliquons sur l'élément de menu "Contact" qui nous affiche la page de contact.
Sauf qu'on a cliqué trop vite, on s'est trompé. On veut revenir en arrière sur la page d'accueil. Cliquons donc en toute logique sur le bouton "Précédent" de notre navigateur préféré... Et là, c'est le drame.
On se retrouve sur la page Default.aspx, alors qu'on s'attendait à retourner sur la page d'accueil...

En effet, pour le navigateur, il n'y a que 2 pages. La page Default.aspx et la page Menu.aspx alors que notre utilisation de l'UpdatePanel pourrait faire croire à l'utilisateur qu'il y en a plus.
De la même façon, si l'utilisateur bookmarque la page de menu et y revient ultérieurement, il sera impossible de savoir dans quel menu l'utilisateur était.
En effet, aucune notion d'état n'est associée à cette page.

2.4.Téléchargement

Vous pouvez télécharger ici les sources du projet de démo : version rar (656 Ko) , version zip (782 Ko).

3.Maintenir l'état de navigation Ajax

Comment faire alors pour maintenir l'état de navigation ?
C'est là qu'interviennent les points d'historiques.

3.1.Maintenir l'état du menu

Pour maintenir l'état, on va utiliser plusieurs choses.
Il faut tout d'abord renseigner la propriété du scriptmanager EnableHistory à true.

 
Sélectionnez

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

Ensuite, lors du click sur le bouton, on va rajouter un point d'historique :

 
Sélectionnez

if (sender == hpaccueil)
{
    phaccueil.Visible = true;
    ScriptManager1.AddHistoryPoint("Menu", "Accueil");
}
else
{
    phcontact.Visible = true;
    ScriptManager1.AddHistoryPoint("Menu", "Contact");
}				

Ainsi, lorsqu'on clique sur un des éléments du menu, on peut observer que l'url a changé, comme on peut le voir sur l'image ci-dessous :

Image non disponible

L'état a été sérialisé (et crypté) et persiste ainsi dans l'url. En cliquant sur le menu Accueil, on a créé un dictionnaire où la clé "Menu" est associée à la valeur "Accueil".
C'est à ça que nous a servi la méthode AddHistoryPoint.
De la même façon, si on clique sur le menu Contact, on va persister la clé "Menu" associée à la valeur "Contact".

3.2.Relire l'état persisté

Faire persister l'état c'est bien, encore faut-il savoir le relire.
C'est ici qu'intervient l'événement OnNavigate du ScriptManager :

 
Sélectionnez

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

Un événement est levé lorsqu'on doit traiter un point d'historique. Le paramètre HistoryEventArgs permet d'aller lire le dictionnaire sérialisé.
Ainsi, on pourra faire :

 
Sélectionnez

protected void Navigate(object sender, HistoryEventArgs e)
{
    phaccueil.Visible = false;
    phcontact.Visible = false;
    if (e.State["Menu"] == "Accueil")
    {
        phaccueil.Visible = true;
    }
    else
    {
        phcontact.Visible = true;
    }
}

Désormais, si vous naviguez entre les menus et que vous cliquez sur le bouton précédent de votre navigateur, le bon PlaceHolder sera affiché.

3.3.Et la date ?

Ceux qui ont fait l'essai se sont rendu compte que la date n'était pas mise à jour. En effet, on ne l'a pas demandé. Et pour cause, nous ne l'avons pas fait persister.
Qu'à cela ne tienne, il est possible d'ajouter plusieurs points d'historiques et ainsi, faire persister plusieurs informations. Rajoutons donc la date :

 
Sélectionnez

protected void ClickLinkButton(object sender, EventArgs e)
{
	if (sender == hpaccueil)
	{
		Show("Accueil", DateTime.Now.ToLongTimeString());
		ScriptManager1.AddHistoryPoint("Menu", "Accueil");
		ScriptManager1.AddHistoryPoint("DateAccueil", dateAccueil.Text);
	}
	else
	{
		Show("Contact", DateTime.Now.ToLongTimeString());
		ScriptManager1.AddHistoryPoint("Menu", "Contact");
		ScriptManager1.AddHistoryPoint("DateContact", dateContact.Text);
	}
}

private void Show(string page, string date)
{
	if (page == "Accueil")
	{
		phcontact.Visible = false;
		phaccueil.Visible = true;
		dateAccueil.Text = date;
	}
	else
	{
		phaccueil.Visible = false;
		phcontact.Visible = true;
		dateContact.Text = date;
	}
}

protected void Navigate(object sender, HistoryEventArgs e)
{
	if (e.State["Menu"] == "Accueil")
	{
		Show("Accueil", e.State["DateAccueil"]);
	}
	else
	{
		Show("Contact", e.State["DateContact"]);
	}

}

Et le tour est joué.

3.4.Fonctionnement

Le clic sur les boutons précédent ou suivant du navigateur ne donne pas lieu à une requête serveur. Comment ASP.NET est-il capable de générer l'événement Navigate qui permet de restaurer notre état ?

Et bien c'est coté client que ca se passe. Le javascript généré par le contrôle ScriptManager va détecter que l'on vient d'une page en ayant cliqué sur précédent ou suivant.
Il va ensuite faire une requête serveur qui va lever cet événement et nous permettre de mettre notre code en réponse à cet événement.

3.5.Téléchargement

Vous pouvez télécharger ici les sources du projet de démo : version rar (656 Ko) , version zip (782 Ko).

4.Conclusion

Cet article montre comment utiliser les points d'historiques pour maintenir des états lors de la navigation Ajax d'un site web.
Tout ce qui a été vu ici concernant les boutons précédent ou suivant de votre navigateur est valable également lors de l'ajout aux favoris de votre navigateur. L'url qui est enregistrée contient l'état sérialisé, ainsi la page ASP.NET a la possibilité de restaurer l'état de la page.
Comme on peut le voir, la mise en place de cette persistance est très simple et apporte un confort de navigation très appréciable dans certains contextes.
N'hésitez pas à vous en servir.

Remerciements

Je remercie l'équipe Dotnet pour leurs relectures attentives du document.

Contact

Si vous constatez une erreur dans le tutorial, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.