Référencement et UpdatePanel : la solution CrawlableLinkButton
Les UpdatePanels sont très agréables pour l'utilisateur puisque, bien utilisés, ils permettent d'avoir une expérience utilisateur plus fluide. Pour arriver à ce résultat, ils utilisent une requête XMLHttpRequest (requête Ajax) qui n'est pas référençable par les moteurs de recherche.
Comment utiliser les UpdatePanels sans sacrifier son référencement ?
Pour qu'un utilisateur puisse utiliser les résultats du moteur de recherche, il faut que le contenu soit accessible à partir d'une requête GET, lorsque l'utilisateur clique il faut qu'il arrive directement sur ce qui l'intéresse, ce n'est pas le cas si on utilise les UpdatePanels avec un LinkButton.
Prenons un exemple : une page qui liste les processus en cours sur la machine et qui affiche le détail du processus sélectionné :
<script type="text/C#" runat="server">
void Page_Load(Object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindData();
}
}
private System.Diagnostics.Process _currentProcess;
public System.Diagnostics.Process CurrentProcess {
get {
if (_currentProcess == null) {
int pid = -1;
if (Page.IsPostBack) {
if (ViewState["PID"] != null) {
pid = (int)ViewState["PID"];
}
} else {
int.TryParse(Request.QueryString["PID"], out pid);
}
if (pid > -1) {
_currentProcess = Processes.Find(delegate(
System.Diagnostics.Process process){
return process.Id == pid;
});
} else {
_currentProcess = Processes[0];
}
}
return _currentProcess;
}
set {
ViewState["PID"] = value.Id;
_currentProcess = value;
}
}
private static List<System.Diagnostics.Process> _processes;
public static List<System.Diagnostics.Process> Processes
{
get
{
// Ne faites pas ça, c'est pour l'exemple mais attention
// c'est pas thread safe ...
if (_processes == null)
{
_processes = new List<System.Diagnostics.Process>(
System.Diagnostics.Process.GetProcesses());
}
return _processes;
}
}
void gvProcess_SelectedIndexChanged(object sender, EventArgs e)
{
// ca aussi c'est mal, c'est pour simplifier l'exemple
CurrentProcess = Processes[gvProcess.SelectedIndex];
}
void BindData()
{
gvProcess.DataSource = Processes;
gvProcess.DataBind();
}
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="CPH1" runat="Server">
<asp:UpdatePanel runat="server" UpdateMode="Conditional" >
<ContentTemplate>
<%=CurrentProcess.ProcessName %>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="gvProcess" />
</Triggers>
</asp:UpdatePanel>
<asp:GridView runat="server" ID="gvProcess" AutoGenerateColumns="false"
OnSelectedIndexChanged="gvProcess_SelectedIndexChanged">
<Columns>
<asp:CommandField ShowSelectButton="true" />
<asp:BoundField DataField="ProcessName" />
<asp:BoundField DataField="VirtualMemorySize" />
</Columns>
</asp:GridView>
</asp:Content>
Cette page fonctionne parfaitement, mais regardons le code html généré au niveau d'une ligne d'un gridview.
<tr class="AspNet-GridView-Alternate">
<td><a href="javascript:__doPostBack('ctl00$CPH1$gvProcess','Select$15')">Select</a></td>
<td>WebDev.WebServer</td>
<td>283324416</td>
</tr>
Le détail de ce processus ne peut pas être indexé par un robot. Même si un robot était capable d'exécuter JavaScript et donc de lire le détail du processus, comment ferait-il ressortir ce résultat ? Lorsque l'utilisateur fais une recherche il veut voir directement le contenu qui l'intéresse : il faut que la contenu soit accessible à partir d'une URL.
Comment contourner le problème ? L'idée est d'exécuter le postback donc du JavaScript pour les clients riches tout en laissant une vrai url pour les robots.
Ainsi la ligne plus haut se transformerait en :
<tr class="AspNet-GridView-Alternate">
<td>
<a href="temp.aspx?PID=1092"
onclick="void(__doPostBack('ctl00$CPH1$gvProcess$ctl17$ctl00',''));return false;">
Select
</a>
</td>
<td>WebDev.WebServer</td>
<td>285884416</td>
</tr>
Pour arriver à ce résultat, j'ai créé un contrôle héritant de LinkButton : le CrawlableLinkButton qui rajoute la propriété NavigateUrl. Les sources du contrôle CrawlableLinkButton se trouve sur aspfr.
Le gridview se transforme alors en
<asp:GridView runat="server" ID="gvProcess" AutoGenerateColumns="false"
OnSelectedIndexChanged="gvProcess_SelectedIndexChanged">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<cs:CrawlableLinkButton runat="server" CommandName="Select"
Text="Select" NavigateUrl='<%# "temp.aspx?PID=" + Eval("id").ToString() %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="ProcessName" />
<asp:BoundField DataField="VirtualMemorySize" />
</Columns>
</asp:GridView>
On voit qu'avec un minimum de code (le CrawLableLinkButton ne fait pas 100 lignes) il est possible de trouver des solutions pour référencer des UpdatePanels, il suffit de se mettre à la place d'un moteur de recherche et analyser le code HTML.