OutputCache et les fichiers .browser – ou comment un UserAgent peut nuire à votre site web
Afin d’optimiser les performances d’une application web, une des bonnes pratiques consiste à rajouter du cache sur les pages ou contrôle utilisateur via la directive @OutputCache. Cette directive permet de mettre le rendu HTML de la page en cache.
Après avoir fait cette opération et passé le site en production, de temps en temps, de façon aléatoire, le HTML que retournait le serveur ne contenait pas les scripts JavaScript propres à ASP.net : fonction __doPostBack , champs cachés __EVENTTARGET, webResources et scriptResource … Je n’ai jamais réussi à reproduire ce comportement sur une plate-forme autre que la production.
Après une séance de décompilation dans Reflector, je me suis rendu compte que ces scripts étaient inclus en fonction du navigateur exécutant la page (Request.Browser).
// pseudo code
internal void BeginFormRender(HtmlTextWriter writer, string formUniqueID){
if (this.ClientSupportsJavaScript){
this.RenderPostBackScript(writer, formUniqueID);
}
}
internal bool ClientSupportsJavaScript
{
get
{
if (!this._clientSupportsJavaScriptChecked)
{
this._clientSupportsJavaScript = (this._request != null) &&
(this._request.Browser.EcmaScriptVersion >= JavascriptMinimumVersion);
this._clientSupportsJavaScriptChecked = true;
}
return this._clientSupportsJavaScript;
}
}
ASP.net génère la variable Browser à partir du UserAgent du navigateur ainsi que des fichiers .browser de votre site web. Par défaut, un site web ASP.net “hérite” des fichiers .browser du dossier %Windows%\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers. Pour certains UserAgent (principalement les robots/crawler), la propriété ClientSupportsJavaScript retourne false.
Du coup, si votre cache est généré à partir d’une requête provenant d’un UserAgent ne supportant pas JavaScript, les scripts ne seront pas inclut pour cette requête. Cependant le rendu HTML de la page sera ajouté au cache et renvoyé pour tous les navigateurs.
Afin de résoudre ce problème, la solution la plus simple est de faire varier votre cache en fonction du type de navigateur. Pour cela, nous pouvons utiliser l’attribut VaryByCustom de la directive OutputCache. Lorsque cet attribut est renseigné, ASP.net va interroger la méthode GetVaryByCustomString du global.asax, cette méthode doit retourner un string servant de clé de cache pour la valeur envoyée.
L’implémentation par défaut contient déjà du code répondant au paramètre browser :
// code provenant de Reflector
public class HttpApplication
{
// ...
public virtual string GetVaryByCustomString(HttpContext context, string custom)
{
if (StringUtil.EqualsIgnoreCase(custom, "browser"))
{
return context.Request.Browser.Type;
}
return null;
}
}
Ainsi, si l’on souhaite faire varier le cache en fonction du navigateur il suffit de rajouter la valeur browser à l’attribut VaryByCustom de la directive OutputCache.
<%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="browser" %>
Le cache sera alors différent en fonction du navigateur : IE, Firefox, mobile, … Si ce comportement ne vous satisfait pas, vous pouvez surcharger la méthode GetVaryByCustomString du global.asax et réécrire le code pour avoir un cache unique sur les principaux navigateurs.
Comme expliqué sur mon précédent poste (OutputCache et ScriptManager – Attention, seul le rendu est caché), l’attribut OutputCache ne fait que cacher le rendu HTML, ASP.net possède un mécanisme permettant de faire varier le HTML en fonction du navigateur, à nous alors de faire attention à ce que l’on met en cache et comment le cache doit varier.
Et vous, avez vous déjà rencontré des problèmes similaires ?