1. Introduction

Depuis l'an 2000 le nombre de navigateurs, ainsi que les différentes versions de ceux-ci, n'a cessé de croître. Chacun ayant ses propres spécificités et respectant les normes W3C jusqu'à un certain degré, il n'est pas toujours aisé de rendre un site compatible avec tous les navigateurs. Bien entendu, les choses évoluent il suffit de regarder les résultats du dernier preview d'Internet Explorer 9 ou bien les excellentes capacités de Chrome ou Firefox 4. Néanmoins, il existe encore de nombreux postes de travail équipés d'anciennes versions, comme Internet Explorer 6 pour ne citer que lui. Tout d'abord, nous allons voir dans cet article les bases de la détection de navigateur en ASP.NET, avec un rappel sur l'entête User-Agent. Nous regarderons ensuite les moyens à notre disposition pour gérer les spécificités des différents navigateurs. Enfin, nous nous attarderons sur les nouveautés apparues en ASP.NET 4.0 à ce sujet. Note : le sujet de l'article n'est pas de produire des feuilles de style CSS compatibles avec les différents navigateurs, mais bel et bien de détecter et gérer ces derniers au cas par cas.

2. User-Agent

Lorsqu'un navigateur adresse une requête à un serveur pour obtenir une page Web, il transmet une chaîne de type texte pour s'identifier. Celle-ci est incluse dans la requête HTTP via l'entête « User-Agent » et elle donne des informations comme par exemple : le nom de l'application, la version, le système d'exploitation, la langue, etc...

Image personnelle

L'image ci-dessus représente le User-Agent pour un navigateur Internet Explorer 8 tournant sous Windows 7. Cette représentation est simplifiée, car dans la réalité le User-Agent va contenir des informations complémentaires sur certaines applications ou composants additionnels installés sur le poste. Prenons un exemple, pour mon portable professionnel le User-Agent est le suivant :

Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C; .NET4.0E; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; InfoPath.3)

On peut déduire de cette chaîne que mon portable :

  • utilise une version 64 bits de Windows 7 grâce à la présence de WoW64 (Windows 32-bit On Windows 64-bit) qui signifie que j'exécute une version 32 bits d'Internet Explorer sur un processeur 64 bits et de NT 6.1 qui indique que c'est un Windows 7. En fait je triche, c'est un Windows Server 2008 R2, mais ce n'est qu'un détail vu qu'il est basé sur la même couche que Windows 7 ;
  • utilise le moteur de rendu Trident 4.0 (Trident/4.0) qui est le moteur de rendu le plus standard (et celui par défaut) d'Internet Explorer 8 ;
  • dispose des frameworks .NET 2.0 (.NET CLR 2.0.50727), .NET 3.0 (.NET CLR 3.0.30729) et 3.5 (.NET CLR 3.5.30729) et 4.0 (.NET4.0C et .NET4.0E) ;
  • dispose du OfficeLiveConnector.1.4 ;
  • etc.

Si vous désirez en savoir plus sur les différentes informations, ces liens vous seront utiles :

Après avoir révisé les bases sur le User-Agent, regardons de plus près quels mécanismes offre ASP.NET pour la détection des browsers.

3. La propriété HttpRequest.Browser

En ASP.NET et depuis la version 1.0, cette propriété représente un objet HttpBrowserCapabilities qui permet de lister les capacités d'un navigateur. Le code ci-dessous permet d'afficher les différentes informations qu'il contient :

 
Sélectionnez

HttpBrowserCapabilities bc = Request.Browser;
Response.Write("<p>Browser Capabilities:</p>");
Response.Write("Type = " + bc.Type + "<br>");
Response.Write("Name = " + bc.Browser + "<br>");
Response.Write("Version = " + bc.Version + "<br>");
Response.Write("Major Version = " + bc.MajorVersion + "<br>");
Response.Write("Minor Version = " + bc.MinorVersion + "<br>");
Response.Write("Platform = " + bc.Platform + "<br>");
Response.Write("Is Beta = " + bc.Beta + "<br>");
Response.Write("Is Crawler = " + bc.Crawler + "<br>");
Response.Write("Is AOL = " + bc.AOL + "<br>");
Response.Write("Is Win16 = " + bc.Win16 + "<br>");
Response.Write("Is Win32 = " + bc.Win32 + "<br>");
Response.Write("Supports Frames = " + bc.Frames + "<br>");
Response.Write("Supports Tables = " + bc.Tables + "<br>");
Response.Write("Supports Cookies = " + bc.Cookies + "<br>");
Response.Write("Supports VB Script = " + bc.VBScript + "<br>");
Response.Write("Supports JavaScript = " + bc.JavaScript + "<br>");
Response.Write("Supports Java Applets = " + bc.JavaApplets + "<br>");
Response.Write("Supports ActiveX Controls = " + bc.ActiveXControls + "<br>");
Response.Write("CDF = " + bc.CDF + "<br>");

Avec ces informations facilement accessibles, il est possible de gérer le rendu des pages d'une application Web en fonction du navigateur et/ou des modules complémentaires installés sur le poste client.

4. Changer dynamiquement la feuille de style CSS en fonction du navigateur

Le code ci-dessous propose une bête solution réutilisable en ASP.NET qui permet de choisir un fichier CSS basé sur le type de navigateur et son numéro de version. Ce code peut-être inclus dans une MasterPage ou un UserControl comme c'est le cas ici :

 
Sélectionnez

<%@ Control Language="C#" %>

<%
if (Request.Browser.Browser.ToString() == "IE")
    if (Request.Browser.MajorVersion < 4 )
        Response.Write("<link rel='stylesheet' type='text/css' href='../Styles/NewSite.css'></link>");
    else
        Response.Write("<link rel='stylesheet' type='text/css' href='../Styles/Site.css'></link>");
else if (Request.Browser.MajorVersion < 5)
    Response.Write("<link rel='stylesheet' type='text/css' href='../Styles/NewSite.css'></link>");
else
    Response.Write("<link rel='stylesheet' type='text/css' href='../Styles/Site.css'></link>");
%>

Pour utiliser cet UserControl que nous allons appeler Browser.ascx, on utilise la classique balise Register. Il faut remplacer le lien statique de la feuille de style actuelle au sein de la balise head. Dans l'exemple suivant, le UserControl est utilisé dans une MasterPage :

 
Sélectionnez

<%@Register TagPrefix="util" TagName="Browser" Src="~/UserControls/Browser.ascx" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
    <title>ASP.NET : Application de test</title>
    <%-- <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" /> --%>    
    <util:Browser runat="server" />
    <asp:ContentPlaceHolder ID="HeadContent" runat="server">
    </asp:ContentPlaceHolder>
</head>

5. Changer dynamiquement la MasterPage en fonction du navigateur

L'astuce qui suit est apparue avec ASP.NET 2.0, à savoir changer dynamiquement de MasterPage dans une Page en fonction du navigateur par le biais d'une syntaxe déclarative. Admettons que j'ai dans mon application Web une MasterPage pour Internet Explorer et une autre pour Firefox dont voici l'aperçu :

 
Sélectionnez

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Firefox.master.cs" Inherits="MasterPageBrowser.Firefox" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
    <title>ASP.NET : Application de test pour Firefox</title>
    <link href="~/Styles/SiteFirefox.css" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder ID="HeadContent" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>

et pour Internet Explorer :

 
Sélectionnez

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="IE.master.cs" Inherits="MasterPageBrowser.IE" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
    <title>ASP.NET : Application de test pour Internet Explorer</title>
    <link href="~/Styles/SiteIE.css" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder ID="HeadContent" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>

Le code suivant montre comme il est facile de faire cela dans la directive Page :

 
Sélectionnez

<%@ Page Title="Home Page" Language="C#" ie:MasterPageFile="~/IE.master" mozilla:MasterPageFile="~/Firefox.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="MasterPageBrowser._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    ...
</asp:Content>

Comme vous pouvez le voir, les directives "ie:" et "mozilla:" permettent de choisir la bonne MasterPage en fonction du Browser. Le tableau ci-dessous résume les différentes possibilités :

Browser ID Browser Name
ie Any version of Internet Explorer
netscape3 Netscape Navigator 3.x
netscape4 Netscape Communicator 4.x
netscape6to9 Any version of Netscape higher than 6.0
mozilla Firefox and other Mozilla-powered browsers
opera Opera
up Openwave-powered devices
nokia Nokia-powered devices
pie Pocket Internet Explorer


Bien entendu, nous aurions pu ne pas utiliser la version déclarative et choisir dynamiquement la MasterPage dans l'event PreInit de la page grâce à l'objet HttpBrowserCapabilities (propriété Request.Browser) comme ci-dessous :

 
Sélectionnez

public void Page_PreInit (Object sender, EventArgs e) 
{
    if (Request.Browser.IsBrowser("IE")) 
        this.MasterPageFile = "IE.master";  
    else if (Request.Browser.IsBrowser("Mozilla"))
        this.MasterPageFile = "Firefox.master";
    else  
        this.MasterPageFile = "Site.master";
}

Notez que l'astuce utilisant la syntaxe déclarative ne s'arrête pas aux MasterPages, mais peut également être appliquée aux contrôles :

 
Sélectionnez

<asp:button id="btnTest" runat="server"
    ie:text="IE Text"
    mozilla:text="Firefox Text"
    text="Default Text"/>

6. ASP.NET 4.0 : Nouveautés concernant les Browser Capabilities

Comme indiqué précédemment, la propriété Request.Browser correspond à un objet HttpBrowserCapabilities. Cet objet obtient des informations du navigateur ou périphérique client lors d'une demande HTTP, indiquant à notre application le type et le niveau de prise en charge proposés par le navigateur.

Les propriétés exposées par l'objet HttpBrowserCapabilities indiquent des fonctions inhérentes du navigateur, mais ne reflètent pas nécessairement les paramètres actuels de celui-ci. Par exemple, la propriété Cookies indique si un navigateur prend en charge des cookies de manière fondamentale, mais il n'indique pas si le navigateur à l'origine de la requête a sa propriété cookies activée.

L'object HttpBrowserCapabilities est piloté par un ensemble de fichiers de définition de navigateur. Ces fichiers, dont l'extension est .browser, contiennent des informations sur les capacités des navigateurs. Avec l'arrivée d'ASP.NET 4.0, ces fichiers de définition ont été mis à jour et contiennent des informations sur les navigateurs les plus récents. La liste suivante montre de nouveaux fichiers de définition de navigateur :

  • blackberry.browser
  • chrome.browser
  • Default.browser
  • firefox.browser
  • gateway.browser
  • generic.browser
  • ie.browser
  • iemobile.browser
  • iphone.browser
  • opera.browser
  • safari.browser

6-A. Utilisation des Providers de Browser Capabilities

Depuis le Service Pack 1 d'ASP.NET 3.5, il est possible définir soi-même les capacités d'un navigateur. En effet, pour une modification appliquée à un serveur Web complet (donc touchant toutes les applications ASP.NET hébergées sur celui-ci), on peut créer ou mettre à jour un fichier .browser dans le dossier du Framework :

 
Sélectionnez

%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\CONFIG\Browsers

Après modification du fichier, la commande suivante, via le prompt de commande Visual Studio, permet de rebuilder l'assembly des Browser Capabilities et de l'ajouter dans le GAC :

 
Sélectionnez

aspnet_regbrowsers.exe -I c 

Pour mettre à jour les Browser Capabilities pour un navigateur donné et pour une seule application ASP.NET, on peut créer un fichier .browser dans le dossier App_Browsers de l'application. Pour une explication plus détaillée du contenu de ces fichiers, vous pouvez consulter cet article MSDN.

Cette approche qui nécessite de modifier des fichiers est fortement source d'erreur. Dans la pratique, peu de développeurs définissent des Browser Capabilities customisés et c'est normal. Les fichiers .browser sont difficiles à mettre à jour, la syntaxe XML peut être complexe à utiliser, et pour être prises en compte les modifications peuvent nécessiter un redémarrage du serveur Web.

Ce qui rendrait le processus plus aisé serait d'avoir une syntaxe commune pour tous les navigateurs, ou de pouvoir stocker ces définitions de Browser Capabilities ailleurs que dans un fichier. Imaginez un Web Service qui vous propose la dernière version des capacités pour un navigateur, ces définitions étant stockées sur SQL Azure par exemple (ou dans une base de données locale) et mises à jour quotidiennement. La donne a changé avec la sortie d'ASP.NET 4.0, cette version inclut une nouvelle fonctionnalité : le Provider de Browser Capabilities. Comme son nom le suggère, ce Provider va nous permettre d'utiliser notre propre code pour déterminer les capacités du navigateur (voire utiliser un futur système de Content Delivery Network pour les Browser Capabilities, à l'instar du Windows Azure CDN, ou des CDN pour les bibliothèques Javascript et JQuery).

Il existe deux approches principales pour l'utilisation de ces Providers : étendre les Browser Capabilities d'un navigateur, ou les remplacer totalement.

6-B. Remplacement des Browser Capabilities

Pour remplacer les Browser Capabilities d'un navigateur, il suffit de créer un Provider personnalisé qui hérite de la classe HttpCapabilitiesProvider :

 
Sélectionnez

public class MonProvider : HttpCapabilitiesProvider 
{ 
  public override HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request) 
  { 
    HttpBrowserCapabilities browserCaps = new HttpBrowserCapabilities(); 
    Hashtable values = new Hashtable(180, StringComparer.OrdinalIgnoreCase); 
    values[String.Empty] = request.UserAgent; 
    values["browser"] = "MonNavigateur"; 
    browserCaps.Capabilities = values; 
    return browserCaps;
  } 
} 

Ce code permet de créer un nouveau Provider de Browser Capabilities et de l'associer au navigateur nommé "MonNavigateur". Pour pouvoir l'utiliser, il faut le référencer dans notre application. Pour ce faire, une petite modification dans notre fichier Web.config (voire Machine.config si besoin est) :

 
Sélectionnez

<system.web> 
    <browserCaps provider="ApplicationBidon.MonProvider, ApplicationBidon, Version=1.0.0.0, Culture=neutral" /> 
</system.web>

Bien entendu, nous avons la possibilité de l'enregistrer dynamiquement au chargement de l'application Web dans le Global.asax, comme dans le code ci-dessous :

 
Sélectionnez

void Application_Start(object sender, EventArgs e) 
{ 
  HttpCapabilitiesBase.BrowserCapabilitiesProvider = new ApplicationBidon.MonProvider();
   // ... 
}

Toutefois, gardez en tête que tout changement dans la classe BrowserCapabilitiesProvider doit intervenir au démarrage de l'application, avant l'exécution de n'importe quel code. Ceci afin d'être sûr que le cache contienne un état valide de l'objet pour traiter les requêtes HTTP.

6-C. Mise en cache des objets HttpBrowserCapabilities

L'exemple précédent a un problème : le code est exécuté à chaque fois que le Provider customisé est invoqué pour obtenir l'objet HttpBrowserCapabilities. De plus, cela peut se produire plusieurs fois au cours d'une même requête. Evidemment, dans le simple exemple précédent, notre Provider nommé MonProvider ne fait pas grand chose. Cependant, s'il devait réaliser un travail plus important, l'impact sur les performances se ferait sentir. Pour éviter cela, nous pouvons mettre en cache les objets HttpBrowserCapabilities :

 
Sélectionnez

public class MonProvider : HttpCapabilitiesProvider 
{ 
    public override HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request) 
    { 
        string cacheKey = BuildCacheKey(); 
        int cacheTime = GetCacheTime(); 
        HttpBrowserCapabilities browserCaps = HttpContext.Current.Cache[cacheKey] as HttpBrowserCapabilities; 
        
        if (browserCaps == null)
        { 
            HttpBrowserCapabilities browserCaps = new HttpBrowserCapabilities(); 
            Hashtable values = new Hashtable(180, StringComparer.OrdinalIgnoreCase); 
            values[String.Empty] = request.UserAgent; 
            values["browser"] = "MonNavigateur"; 
            browserCaps.Capabilities = values; 
            HttpContext.Current.Cache.Insert(cacheKey, browserCaps, null, DateTime.MaxValue, TimeSpan.FromSeconds(cacheTime));
        } 
        
        return browserCaps; 
    } 
} 

Dans l'exemple ci-dessus, le code génère une clé de cache et y ajoute l'objet HttpBrowserCapabilities. Celui-ci peut ensuite être récupéré du cache et utilisé dans les multiples requêtes qui utiliseront le Provider MonProvider.

7. Etendre les Browser Capabilities ASP.NET

Le paragraphe précédent décrit comment créer un nouvel objet HttpBrowserCapabilities en ASP.NET 4.0. Le Provider MonProvider correspond en fait au Browser MonNavigateur. Nous allons voir maintenant comment étendre à la volée les définitions des navigateurs déjà définis en ASP.NET. Je vous vois venir avec une remarque du style "oui, mais il va falloir mettre les mains dans le cambouis et trafiquer les fichiers .browser XML". Et bien rassurez-vous : non. Un exemple sera plus parlant :

 
Sélectionnez

public class MonProvider : HttpCapabilitiesEvaluator 
{ 
    public override HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request) 
    { 
        HttpBrowserCapabilities browserCaps = base.GetHttpBrowserCapabilities(request);
        if (browserCaps.Browser == "Unknown") 
            browserCaps = MonBrowserCapabilitiesEvaluator(request); 

        return browserCaps; 
    } 
}

Dans le code, la fonction GetBrowserCapabilities va tenter d'identifier le browser de la requête. Si toutefois aucun browser n'est identifié, alors nous utilisons notre propre Provider nommé MonBrowserCapabilitiesEvaluator pour identifier le browser de la requête.

7-A. Ajout de fonctionnalités à un Browser Capabilities existant

L'exemple ci-dessous permet de rajouter la capacité MultiTouch à un Browser existant. Le principe est simple, on ajoute au à l'object browserCaps.Capabilities, qui implémente IDictionary, une nouvelle paire clé/valeur :

 
Sélectionnez

public class MonProvider : HttpCapabilitiesEvaluator
{
    public override HttpBrowserCapabilities GetBrowserCapabilities(HttpRequest request)
    {
        HttpBrowserCapabilities browserCaps = base.GetHttpBrowserCapabilities(request);
        browserCaps.Frames = true;
        browserCaps.Capabilities["MultiTouch"] = "true";
        return browserCaps;
    }
}

VIII. Conclusion

Grâce aux nouvelles fonctionnalités apparues en ASP.NET 4.0, nous pouvons maintenant modifier facilement les Browser Capabilities d'un navigateur. Nous pouvons aussi gérer à notre manière et selon nos besoins l'identification d'un navigateur. Mais aussi, pour aller plus loin, ces nouveautés nous permettent de stocker et partager nos définitions des capacités d'un navigateur.

Liens

Remerciements

Je tiens à remercier eusebe19 pour la relecture.