Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Optimisation du ViewState

Poster un commentaire

Le ViewState est une pièce maitresse dans l’architecture Asp.Net et pose souvent de véritable problème en devenant parfois plus volumineux que le reste de la page.

Une technique que je vois utilisée est de le désactiver sur chaque composant ce qui ouvre la porte à d’autres problèmes puisqu’il faut tout restester avec soin et parfois on constate que le composant est incapable de fonctionner ainsi.

Plutôt que de supprimer le ViewState on pourrait peut être envisager d’optimiser son contenu. Après tout dire: je désactive/active le ViewState n’est pas à proprement parler correct. Comme tous les éléments qui composent une page Web, il a un cycle de vie, c’est à dire qu’il est activé à un endroit précis du cycle de la page hôte.

Cette remarque est la base de ce qui va suivre. Mais nous allons commencer par examiner les outils avant de voir comment les exploiter.

C’est quoi un ViewState?

Le ViewState se présente sous la forme de la propriété du même nom de la classe Page. Il s’agit d’un dictionniaire clef/valeurs stocké par défaut sur la page Web dans un encodage 64 bits. On peut aussi le crypter ou bien le sauvegarder sur le serveur.

Le ViewState est donc un conteneur dont la portée est la page. Il n’est pas possible de le partager sur deux pages différentes.

On stocke quoi dans le ViewState?

Beaucoup de développeurs pense que le ViewState sert à organiser la persistance des pages. Notez le tout de suite: c’est faux! Il ne joue même qu’un rôle assez mineur dans cette histoire.

Le mieux est d’en apporter la preuve en construisante page en pur HTML:

html-viewstate

Tout ce que vous avez à faire est de l’héberger dans IIS. Notez bien qu’à aucun moment on utilise .Net dans cette histoire.

Le test est le suivant:

  1. Vous lancez la page et faites une saisie
    html-saisie
  2. Vous cliquez sur le bouton

On constate que les zones précédemment saisies sont vides. Celà semble appuyer la thèse selon laquelle le ViewState joue un rôle dans la persistance.

Oui, mais observez l’url et la chaîne de connexion on y voit tout de même ceci:

http://localhost/test.htm?T2=petits&T4=cochons&T5=tra+la+la

On retrouve une partie des saisie, mais pas toutes. Lesquelles?
Vous avez peut-être remarqué que les zones de saisie correspondent à toutes les combinaisons possibles de cohabitation des attributs name et id. Et on constate que seules les zones dotées d’un attribut name ont été sauvegardée sans la chaîne de requête.
Vous pouvez refaire le même test avec le verbe POST cette fois et examiner le header avec Fiddler, vous retomberez sur la même conclusion:
html-post
  • La persistance est une propriété intrinsèque d’HTTP.
  • Seules les zones de saisies avec un attribut name sont persistées
Le ViewState ne joue aucun rôle dans cette histoire. Ce n’est même pas lui qui est responsable de la copie des données de la requête dans les zones de saisie lors du réaffichage des pages.
Mais il sert à quoi au juste?
Pour l’essentiel à sauvegarder des états qui ne sont pas transmis dans la requête HTTP. C’est à dire que le ViewState nous évite de créer des tas de champs hidden un peu partout comme on le faisait autrefois.
Par exemple le résultat d’un calcul effectué côté serveur que l’on ne peut ou ne souhaite refaire à chaque renvoi de la page.
Que devons nous savoir d’autre sur le ViewState?
Pour aller plus loin on a tout de suite besoin de comprendre plusieurs choses:
  • ViewState est une propriété de type StateBag. Cela signifie qu’il peut suivre les changements des états d’affichage qu’il a enregistré.
  • Le suivi doit être activé par un appel à la méthode TrackViewState()
  • La propriété Dirty d’un membre suivi passe alors à true si il a été modifié et si le suivi a été activé
Examminons une partie du cycle de vie d’une page Asp.Net avec des composants.
cyclevie
En vert les événements correspondant à la page, en bleu ceux des contrôles déposés sur la table. En rouge les événement qui ne sont levé qu’au moment du renvoi de la page.
Nous remarquons tout de suite ceci:
  • On observe tout de suite que la méthode TrackViewState est lancée automatiquement par la plomberie Asp.Net. On a peu de raison de le faire soi-même et c’est rarement une bonne idée.
  • L’activation est faite tout de suite après l’événement Init
  • Lorsque la page atteind son événement Init, le ViewState des contrôles est déjà activé.
Peut-être qu’une petite expérience va nous aider à matérialiser ces remarques.
Revenons dans notre bon vieux Visual Studio on va construire une page Asp.net suivante:
codedemo1
La propriété message est juste une chaîne très longue pour faire du volume dans le ViewState.
On ajoute également un bouton de soumission sur la page afin de déclencher un renvoi.
On surveillera la valeur de dirty et ViewState[« test »].
Dans un premier temps mettons en commentaire Page_Load et lançons la page. A la sortie de OnInit nous avons ceci:
dirty1
On observe que ViewState contient bien le message, mais dirty vaut false. Côté code HTML on trouve:
<input type= »hidden » name= »__EVENTVALIDATION » id= »__EVENTVALIDATION » value= »vEYI3Mw3yBhp909BROBCNt8pS3CdWndPan91US2hjHy5vT5dFdH+c/7MoQtIKZIEP0mAXUKbtmQivpd1iTt5rKFD9myNKk8NvhrGTztz2dzGgjxCxKDN8BJCAWh5k » />
Au vu de la taille il est peu probable d’y retrouver notre message, de fait si je déclenche un renvoi, ViewState[« test »] est null dans Init et Load. C’est donc conforme à ce que nous avons déjà expliqué.
Cette fois on commente OnInit et on travaille avec Page_Load. On note que cette fois TrackViewState a été lancé ce qui signifie que le ViewState trace les modifications des états qu’il contient.
Dirty vaut false en arrivant dans Page_Load. Le fait d’ajouter le message le fait basculer à true. On s’attend donc à ce que le message soit copié dans la page ce qui est confirmé par:
<input type= »hidden » name= »__VIEWSTATE » id= »__VIEWSTATE » value= »/cE5/HfFToRYA84mEwHD1WGzX+BIcU/LxSNNRi3VFJJ5u4E+zP2kOiWHNgr+0oNnpXLo8yHYrd9pskH23bXNdnh7YATsCHstjoMputgAPHNjMd/N/W6Anu49kCHxM9QtJKdMztK4YfGDaPbN9r7vgnhsoIhN1jLs7mw2WMntFsXi4ZTBRVhucgG6tNLH7gILDz/IMtUMCGkrfJeOBkbpkdNQJ7hqMfoFAXk4YQ7xYGODfrwGnXr4iD/fgXZ0zgaHS19wvYTqtHhwldcsDYbVviuMDLa5ou+BhM/caVxjFByWkzx/9phD9mHIPNU2ki+3F6aGaSKapPMvoctp67WP7hzvnqUAZpAwvfWLjFiQ= » />
Si je fais un renvoi:
En entrée de Page_Load j’observe ceci:
dirty2
Le ViewState est effectivement restauré.
Voilà, nous avons tous les éléments du puzzle, il ne reste plus qu’à le mettre en place et optimiser notre ViewState.
Que fait t’on avec tout cela?
On va examiner un exemple classique: une liste déroulante que l’on doit alimenter.
La page est construit de la façon suivante:
ddlinside
Cette façon de faire convient parfaitement à des données statiques: par exemple une liste de pays…
Avec les données le viewState vaut:
tmTP1m0sRhb3D2GA6AWP0WbReHlHbbD9ZtljpjE+N25qAmPI5PQA+oly2dXGe/GuoosLv7I5UKmUDeDDU6Q004kqoBxorJCEt8hQqe+IdP85cTuTi6JBRynH9
jOS+JjiWZXOt53ugPLJJHVQY1RLqBeAysyMLSIU51HwVeuE7uJz/cKB1fE/iyuVpIglISBJ
En augmentant le nombre d’items dans la liste on vérifie facilement que le ViewState ne voit pas sa taille changer. Par conséquent les données ne sont pas sauvegardées dedans parce qu’elle sont chargées durant l’événement Init du contrôle qui a lieu avant l’appel à la fonction TrackViewState qui active le suivit des états dans le ViewState.
Il faut être clair la dessus, on parle de l’événement Init du contrôle, pas celui de la page et c’est important.
Supposons par exemple que l’alimentation a lieu depuis le code.
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    DropDownList1.DataSource = GetData(5);
    DropDownList1.DataBind();
}
Le ViewState obtenu est le suivant:
XzAArZj5Sc1NN4YgequWXqKPsM7gKRzK3tAWiE+Dk4qHJb29Ie2ymKWIp8kGzF1OhY0ScOIHLV3vTdfXBbfhdagEcrYbBcU9Uho9Bk+YCC0ljprItSgFUDhXdiEKW9ak1WcWNfcD44eK02dnmaWd
CWdU4oWW1t9fCicNRBKG1FkUiviEDOyL4Paxr8LiXa8G+Yni8Sda39H37QLye3inwX3mRlIoSWo9v+QK/Fc2r6eaQyPj2mfA1tme60OFPNNJdP8Bn40RJAz8GkfjKLrZOz8SQ5srKGKkt9LlebAmuec
yh+gTpHqthqhJHm65YTn4FWghwTqJpKr2X7i+PE4P6Dn3roA5cgO+bDrVmP5Rc6p/8uAxiooMpT5hp/sE2T3h7bP3gf9mPhvObvUldCfTP0k8U7/INGVT10YCewfhm0kZ1wwbZfom8c3wlhsYd1r
WNv6VYWUvwV6sX5vyEudo/A==
La taille dépend du contenu de la liste déroulante.
Il s’agit certes de l’événement Init, mais celui de la page qui, si vous vous reportez à la liste des événements précédemment présentées, a lieu après que le contrôle a appelé TrackViewState.
Si on voulait garder l’avantage du chargement dynamique des données, mais pas perdre le remplissage dans le Init du contrôle il faudrait créer un contrôle personnalisé:
public class NewDdl:DropDownList
{
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        DataSource = Default.GetData(5);
        DataBind();
    }
}

Et cette fois on trouve:

SRONuDXKH6z8lWthe8z7fsX+biLCE9s3ZMhQoO9HKKr4qlnUc1zWS4dPmNJNXxxUyxG9pYjZCE6NzC3Am8/arTO1LK04b/DLM3c1Iafqv0yPf8k213CSWWexY7JjL/epuc1j9

oKbnamgciOO1KyG2OoP7CdL6P8JCbN515QgFF2AUrS1toYZJyPaaNIaNHDT

Pas de sauvegarde en ViewState!

La conclusion est simple:

Pour alimenter un contrôle sans surcharger le ViewState vous devez:

  • Alimenter le contrôle durant SON événement Init
  • Accepter de devoir recharger les données à chaque rechargement de la page

Si le coût de récupération est élevé, il faudra penser Cache.

Je vois parfois du code comme celui-ci vu plus haut:

protected void Page_Load(object sender, EventArgs e)
{
     DropDownList1.DataSource = GetData(5);
     DropDownList1.DataBind();
}

Ce code est malencontreux pour plusieurs raisons:

  • Vous payez le coût du ViewState, mais sans en profiter puisque vous réinterroger la base de données au lieu de récupérer le ViewState
  • Pire encore: si vous faites un postback, vous constaterez que la sélection dans la liste déroulante a été perdue ce qui n’est pas forcément l’effet attendu

Il faut absolument écrire ceci:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        DropDownList1.DataSource = GetData(5);
        DropDownList1.DataBind();
    }
}
Publicités

Laisser un 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 )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

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

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s