Ajax: UpdatePanel Injection

Je vais vous présenter une technique utile pour optimiser la gestion des UpdatePanel dans ASP.NET Ajax : l’injection d’UpdatePanel.
[Le code source est disponible en fin d’article]

La problématique :

Dans une page se trouve une liste d’éléments présentés dans un contrôle. Prenons un contrôle template comme le Repeater par exemple (bien que je préfère créer ma propre liste templatée). Une action sur un élément doit changer son état et l’affichage doit refléter ce changement en conséquence. L’image ci-dessous montre l’exemple utilisé.

Avec un PostBack classique, on doit reconstruire tout l’arbre de contrôle pour pouvoir rendre la liste modifiée. Une optimisation (même si on ne gagne que de la bande passante) consisterait à placer le template de l’élément dans un UpdatePanel mais on ne gagne pas le temps de Render car la page reconstruit quand même l’arbre de contrôle (CreateChildControl) comme présenté dans la trace ASP.NET suivante.

arbre de controle du repeater

En plus, à moins que vous ne l’ayez prévu dans votre code, il y aura un nouvel appel à la base de données pour recharger la liste.

L’objectif :

Ne pas reconstruire l’ensemble de l’arbre (le Repeater) mais construire juste le template de l’élément modifié. On envoie le HTML généré au client via AJAX pour mettre à jour le DOM.

La solution :

On va utiliser un faux UpdatePanel qui représente la partie à modifier et qui sera plus haut dans la hiérarchie de contrôles (à la racine par exemple). On intercepte l’événement dans la page, on effectue la modification visuelle (après avoir fait son traitement métier bien évidemment) dans ce faux UpdatePanel et on va le réinjecter côté client à la place du vrai UpdatePanel à modifier. Bien entendu, on ne recharge pas la liste d’éléments.

Comment faire ? :

Premièrement, il faut placer la partie à mettre à jour selon cette technique dans un UserControl (ASCX) pour factoriser; je rappelle qu’il y a les instances dans le Repeater et la fausse instance à la racine. Dans notre cas c’est le bloc jaune (l’élément) que j’exporte. Cet export nous oblige à réifier l’affichage d’un élément en lui créant par exemple des propriétés publiques correspondant aux infos à afficher. On peut aussi le rendre autonome afin qu’il sache les retrouver à partir de l’identifiant de l’élément mais là je rentre dans le détail. Il faudra aussi faire de même avec les événements que l’élément peut lancer.

Deuxièmement, on place un instance de ce contrôle dans l’ItemTemplate du Repeater et à la racine de la page, dans un UpdatePanel. Pour le second, on veillera à mettre Visible à False afin de ne pas perturber l’affichage normal. Je vous rappelle que ce second élément n’est là que pour simuler un autre UpdatePanel, c’est un contrôle « technique« .

<ItemTemplate>
 <Custom:Department runat="server" ID="DeptBlock" IsSelected='<%#Eval("DepartmentID").ToString()==ElemID %>'
   ElemGroup='<%#Eval("GroupName") %>' ElemName='<%#Eval("Name") %>' ElemID='<%#Eval("DepartmentID") %>' />
</ItemTemplate>

Troisièment, désactiver les raffraichissements automatiques des UpdatePanel pour prendre la main avec les attributs suivants :

ChildrenAsTriggers="false" UpdateMode="Conditional"

Quatrièment, il ne reste plus qu’à modifier l’événement déclencheur (j’ai choisi ici ItemCommand du Repeater). Les étapes sont :

  • désactiver le Repeater pour prendre la main
  • remplir le faux UpdatePanel
  • faire croire au système que c’est le bon UpdatePanel que l’on envoie
  • L’envoyer côté client

protected void DepartmentList_ItemCommand(object source, RepeaterCommandEventArgs e)
{
 //on ne rebind PLUS la liste
 // DepartmentList.DataBind();
 //et on la "désactive" sinon, elle a tendance à se construire toute seule
 DepartmentList.DataSource = null;
 DepartmentList.DataSourceID = null;

 //en revanche, on configure le FakeUpdatePanel
 //...ici, je mets des données bidon mais je devrais charge mon élément individuel
 FakeDepElem.ElemGroup = "FAKE MIS A JOUR";
 FakeDepElem.ElemName = "Fake Dept";
 FakeDepElem.ElemID = e.CommandArgument.ToString();
 FakeDepElem.IsSelected = true;
 //on rend l'élément visible sinon la méthode Render ne retournera rien
 FakeDepElem.Visible = true;

 //ICI, c'est le point technique, on change l'ID du faux UpdatePanel
 //le fait de changer l'identifiant de l'update panel va permettre de cibler un autre updatepanel coté client.
 FakeElementUP.ID = e.CommandName;
 //on envoie le panel au client
 FakeElementUP.Update();
}

Le point le plus important est le remplacement de l’identifiant de mon faux UpdatePanel. Une fois renvoyé au client, la partie JavaScript d’Ajax cherche l’UpdatePanel spécifié et le remplace, sans se rendre compte de l’altération. La capture faite avec Fiddler ci-dessous montre ce que renvoie Ajax au client. Une entête identifie le bloc (l’UpdatePanel) à mettre à jour suivi du code HTML à remplacer. C’est cette entête finalement que nous avons altéré.

632|updatePanel|DepartmentList_ctl03_DeptBlock_ElementUP|

Verification avec Fiddler

Dans ce code, vous voyez que j’utilise e.CommandName. Ce choix est arbitraire. Pour fonctionner, le handler de l’événement a besoin à minima des informations suivantes :

  • l’identifiant de l’élément (ou un moyen de le retrouver)
  • l’identifiant de l’UpdatePanel à mettre à jour

SelectBtn.CommandArgument = ElemID;
SelectBtn.CommandName = ElementUP.UniqueID;

Moi je les passe explicitement dans la commande mais on peut imaginer de les retrouver avec le paramètre source ou par un autre moyen.

J’ai pu mettre en oeuvre cette technique massivement dans un site web grand public de courses en ligne que nous (Bewise) avons réalisé pour un client. Je peux donc vous dire qu’elle a été validée à tous les niveaux : performances, maintenabilité, fiabilité, sécurité. Bien évidement, elle est beaucoup plus complexe que cela car beaucoup plus riche fonctionnellement (je dis cela pour ceux qui peuvent rester sur leur faim).

Effets de bord :

Si la Trace est active, vous risquez de voir apparaître une erreur précisant qu’il y a des doublons dans les ID des contrôles et le module de Trace ne sait pas le gérer (et donc lève une exception). Bizarrement, l’erreur n’est plus levée si la pile est pleine (requestLimit). Si vous avez plus d’infos sur ce comportement non déterministe ou un moyen de contournement…

Code Source :

Le code source utilise juste une connexion à la base AdventureWorks de Microsoft. Vous aurez juste à changer la chaîne de connexion dans le web.config et le nom du serveur web dans les propriétés du projet. Le code est téléchargeable ici

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s