Tutoriel : Permettre la navigation et le bookmarking avec une application ASP.NET Ajax

Nico-pyright(c)

Cet article présente une solution pour gérer l'historique dans un site ASP.NET utilisant de l'Ajax.
Commentez cet article : Commentez ♪

- Titre : Tutoriel : Permettre la navigation et le bookmarking avec une application ASP.NET Ajax

- Auteur : Nico-pyright(c)

- Parution : 15 avril 2009

- Mise à jour : 15 avril 2009

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- 1.Introduction

- 2.Le problème

- 2.1.L'exemple d'une navigation par menu

- 2.2.Chargement partiel des pages

- 2.3.Et le bouton « Précédent » de mon navigateur ?

- 2.4.Téléchargement

- 3.Maintenir l'état de navigation Ajax

- 3.1.Maintenir l'état du menu

- 3.2.Relire l'état persisté

- 3.3.Et la date ?

- 3.4.Fonctionnement

- 3.5.Téléchargement

- 4.Conclusion

- Remerciements

- Contact

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.

À 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

2.1.L'exemple d'une navigation par menu

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.

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

<%@ 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.

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;
}
}

2.2.Chargement partiel des pages

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.

<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 clic sur un menu, on constate le rechargement partiel, comme l'indiquent les heures différentes du Label témoin et du Label du PlaceHolder.

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 :

<%@ 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 deux 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.

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

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

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 :

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 :

<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 :

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 :

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 côté client que ça 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 tutoriel, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.