Business Geek

Aller au contenu | Aller au menu | Aller à la recherche

mercredi 16 janvier 2008

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

dimanche 2 septembre 2007

Architecte ASP.NET du site Express Drive

Un petit peu de pub pour un client, pour Bewise (et un peu pour moi :-) avec la présentation du site Express Drive.

Express Drive est un site de courses en ligne tout ce qu'il y a de plus classique sauf que derrière se cache un concept excellent : le drive. Vous faîtes vos courses sur le web et vous passez les prendre au magasinExpress Drive de E. Leclerc Roques. Même pas besoin de se garer, vous allez dans les voies réservées et n'avez qu'à ouvrir votre coffre.

Pas besoin de citer le nom du client, c'est écrit sur la photo. Actuellement, il n'y a qu'un seul magasin pilote en france (et c'est ici, près de Toulouse) mais nul doute que les prochains ouvriront bientôt près de chez vous.

Je suis fan de ce concept mais ce qui me rend fier c'est que j'ai été l'architecte technique du site web Express Drive et Bewise est en grande partie responsable de la mise en oeuvre (merci Jocelyn). Nous sommes heureux d'avoir pu accompagner notre client à développer son site Web en un temps record.

Et comme un blog de geek ne peut pas se contenter de cette simple annonce, ouvrons un peu le capot du site...

Sans entrer dans le détail, ce site est 100% XHTML + CSS 2.0 (bon d'accord, on a dû prendre quelques raccourcis de temps en temps). Le site tire aussi énormément partie des extensions AJAX de ASP.NET. En particulier la page de courses qui est 100% en AJAX et où nous avons poussé à fond l'utilisation et la maîtrise des UpdatePanel.

Ce site n'est qu'un premier jet et va s'enrichir de nombreuses fonctionnalités pour atteindre le niveau de ces concurrents Houra, Ooshop, AuchanDirect, etc.

vendredi 3 août 2007

Je n'aime pas les FormView

Même si je fais du web de façon intensive depuis plusieurs années, je n'ai jamais creusé les contrôles de databinding tout prêt de ASP.NET (ni 1.1, ni 2.0). Je peux même dire que je suis novice avec les GridView, FormView et autres DetailView.

Je suis confronté à ces contrôles depuis quelques mois maintenant dans le cadre de projets qui en tirent parti. Je dois dire que je commence à être séduit par ce mode de fonctionnement même si je reste accro aux "custom templated controls".

Sauf que la semaine dernière j'ai découvert que la FormView a un fonctionnement que je n'aime pas du tout et qu'il est important de connaître pour bien maîtriser ce que l'on fait. Admettons que dans la FormView, vous placiez un LinkButton et un CustomValidator pour vérifier une condition. Le LinkButton a la propriété CausesValidation à true. Vous branchez l'événement click sur votre méthode et là, tout bascule, en déboguant, vous vous apercevez que la validation (votre méthode OnServerValidate) est appelée 2 fois.

La validation est garantie par la propriété CausesValidation du bouton comme on peut le voir ci-dessous dans la méthode RaisePostBackEvent du LinkButton via Reflector.

protected virtual void RaisePostBackEvent(string eventArgument)
{
    base.ValidateEvent(this.UniqueID, eventArgument);
    if (this.CausesValidation)
    {
        this.Page.Validate(this.ValidationGroup);
    }
    this.OnClick(EventArgs.Empty);
    this.OnCommand(new CommandEventArgs(this.CommandName, this.CommandArgument));
}

Mais qui appelle donc la validation une seconde fois ? On peut voir aussi dans cette méthode qu'un LinkButton lève l'événement Click mais aussi l'événement Command, même si on n'a pas précisé de CommandName. Le problème, c'est qu'un événement Command utilise le mécanisme de Bubbling (méthode OnBubbleEvent) et remonte dans la hierarchie de contrôle jusqu'à ce que quelqu'un l'intercepte. Et devinez qui le prend en charge ? La FormView !!

Sans vérifier si la FormView est à même de prendre en charge la commande (représentée par le paramètre CommandName), elle lance la validation comme on peut le voir dans le code de la méthode OnBubbleEvent().

if (causesValidation && (this.Page != null))
{
   this.Page.Validate(validationGroup);
}

Ce qui fait que si vous préférez utiliser l'événement Click, vous aurez 2 validations de la page (ou du ValidationGroup).

La solution :

  • utiliser l'événement Command de la FormView pour faire vos traitements dans les boutons se trouvant en son sein.
  • mettre la propriété CausesValidation à false et appeler manuellement Page.Validate() dans la méthode qui gère l'événement Click

Moralité : FormView stinks !

mercredi 1 août 2007

unable to build project output group 'content files from WebApplication'

Dans un projet de setup web, quand vous souhaitez copier les 'Content Files' des sorties d'un projet de type Web Application, si vous rencontrez un jour cette erreur, je vous propose la solution :

unable to build project output group 'content files from WebApplication'

Le problème vient du fait qu'un fichier du site Web n'est pas trouvé sur le disque (le petit icône avec un triangle jaune). La compilation du projet web ne génère aucun warning mais un projet de setup lève une erreur car lui a besoin du fichier manquant.

Plutôt que de vous arracher les cheveux, naviguez dans tous les répertoires du site dans le Solution Explorer (je sais ça peut être long) à la recherche du triangle jaune (ça ferait un bon titre pour un film) et supprimer cette référence.

Tips : cherchez en priorité dans le répertoire des images, c'est souvent là qu'il y a le plus de boxon fichiers.

 

jeudi 28 juin 2007

Ajouter une CSS dans une page avec une MasterPage

Quand on utilise les MasterPages, il devient impossible d'ajouter un lien vers une feuille de style directement dans une page. En effet, on ne peut mettre la référence à la CSS que dans la section <head/> et les <asp:Content> des pages enfants doivent se trouver obligatoirement dans le <body/> (la <form/> plus exactement).

On a tout de même accès à la section <head/> depuis le code pour ajouter manuellement un lien vers une CSS avec un contrôle de type HtmlLink.

HtmlLink cssLink = new HtmlLink();
cssLink.Href = "../mapage.css";
cssLink.Attributes.Add("rel", "stylesheet");
cssLink.Attributes.Add("type", "text/css");
Page.Header.Controls.Add(cssLink);

Afin de rendre cette utilisation plus souple, on peut encapsuler ce code dans un contrôle afin de l'ajouter graphiquement dans le designer de page :

public class StyleSheetLink : Control
{
  private string _href;
  [UrlProperty]
  public string Href
  {
    get { return _href; }
    set { _href = value; }
  }
  protected override void OnPreRender(EventArgs e)
  {
    HtmlLink cssLink = new HtmlLink();
    cssLink.Href = _href;
    cssLink.Attributes.Add("rel", "stylesheet");
    cssLink.Attributes.Add("type", "text/css");
    Page.Header.Controls.Add(cssLink);
  }

Ainsi, il suffit d'ajouter dans une page le tag suivant :

<Bewise:StyleSheetLink ID="css" runat="server" Href="~/_css/default.css" />

Pour améliorer ce contrôle, on pourrait ajouter une vérification dans la liste des contrôles du header pour savoir si la CSS n'est pas déjà référencée.

mardi 12 juin 2007

Afficher un PNG avec un channel alpha sur IE6

La problématique

Elle est très simple, IE6 ne gère pas le channel alpha sur une image au format PNG. Je ne parle pas de simple transparence (ça c'est géré) mais d'une transparence en dégradé. (définition wikipedia)

La base de travail

Nous allons faire fonctionner le channel alpha pour une image classique et pour image en background. Le code HTML est le suivant :

<img src="images/logoalpha.png" />

<h1><span>Les passionnés du malt</span></h1>

Le code CSS est le suivant :

#headings h1 {
  height:241px;
  width:512px;
  background: url( '../images/logoalpha.png' ) no-repeat;
}
#headings h1 span {
  display: none;
}

La solution

Internet Explorer dispose d'un système de filtres pour effectuer des opérations de rendu sur des éléments de l'arbre DOM. Notamment, il existe un filtre appelé AlphaImageLoader qui permet de gérer le channel alpha. Pour appliquer un filtre, il suffit d'insérer une directive CSS filter. Pour l'image background, c'est assez simple, on applique directement la directive à la place de background :

filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/logoalpha.png', sizingMethod='scale');

Cependant, pour rester compatible avec IE7 et Firefox, on doit mettre la directive dans un hack pour IE et on garde un background classique pour les autres navigateurs. Le hack qui est interprété uniquement par IE (inférieur à la version 7) est "* html" :

* html #headings h1 {
  /* on annule le background et on le red‚finit avec le filtre */
  background:none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/logoalpha.png', sizingMethod='scale');
}

Pour l'image classique, on ne peut appliquer directement la directive filter en CSS. On doit recourir à du Javascript pour appliquer le filtre. Il existe un moyen d'appliquer un script sur un élément avec IE6, c'est l'utilisation des behaviours. Un behaviour est un bout de Javascript encapsulé dans un fichier .HTC formaté d'une certaine façon :

<public:component>
  <public:attach event="onpropertychange" onevent="propertyChanged()" />
  <script>
[MON SCRIPT ICI]
  /script>
</public:component>

On voit ici que le script sera exécuté au changement d'une propriété du tag sur lequel il est appliqué. Je ne vais pas décrire le script ici et je vous propose plutôt de le télécharger ici. Retenez juste qu'il a besoin d'un gif transparent pour appliquer le filtre (je n'ai pas cherché à comprendre ;-).

Mais alors, comment appliqué le behaviour ? En CSS, il suffit d'ajouter :

.alphaPNG {
  /* compris uniquement par IE --> javascript executé sur l'élément */
  behavior:url('png.htc');
}

Vous aurez compris que j'ai juste à appliquer la classe CSS sur mon image PNG :

<img src="logoalpha.png" width="512" height="241" class="alphaPNG" />

Vous noterez que je fixe la taille sinon, le rendu ne se fait pas.

Pour voir ce que cela donne, rendez-vous sur cette page : http://blog.djeepy1.net/public/labs/png-fix/SampleLogo.htm
Pour télécharger tout le projet :  http://blog.djeepy1.net/public/labs/png-fix/sample.zip

Merci à Mitch pour le logo.

 

dimanche 10 juin 2007

Compilation conditionnelle en ASP.NET 2.0

Je me suis retrouvé récemment face à un doute technique. Après avoir longuement expliqué l'intérêt de mettre des traces dans les différents événements d'une page ou d'un contrôle ASP.NET et l'importance de conditionner ces traces pour ne pas les retrouver dans l'environnement de production, je me suis retrouvé coincé car la technique a changé entre ASP.NET 1.x et ASP.NET 2.0. Explication...

Dans ma page, je mets les traces souhaitées :

#if TRACE
    Context.Trace.Write("MaPage", "Début de l'événement OnLoad");
#endif

Les pages et les contrôles ASCX étant compilés dynamiquement à l'exécution, il est inutile de mettre une directive de compilation dans votre projet (sauf si vous utilisez la fonctionnalité de publication avec les pages précompilées et non updateable).

En 1.x, j'avais l'habitude de configurer la directive dans la section <compilation/> du web.config :

<compilation debug="true" compilerOptions="/d:TRACE">

Là, l'intellisense ne me propose pas l'attribut CompilerOptions ce qui m'est confirmé par une erreur au runtime. Un petit tour sur la toile pour comprendre que la compilation est plus complexe (fine) en ASP.NET 2.0.
Heureusement, il existe toujours cette configuration dans la directive de page, ce qui n'est pas plus mal d'ailleurs pour restreindre le scope de la directive de compilation :

<%@Page Language="C#" CompilerOptions="/d:TRACE" %>

Pour ceux qui souhaitent utiliser la directive pour l'ensemble du site, il faut vous tourner vers la section de configuration <system.codeDom> où on doit déclarer des <compilers> avec leurs paramètres pour des jeux de fichiers du site. dans l'exemple ci-dessous, on définit que les fichier .cs seront compilés via un CSharpCodeProvider (csc.exe) avec la directive TRACE.

<system.codedom>
   <compilers>
      <compiler            
         language="c#;cs;csharp" extension=".cs"
         compilerOptions="/d:TRACE"
         type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />          
   </compilers>
</system.codedom>

Attention toutefois avec cette méthode car le site doit être exécuté en FullTrust pour avoir accès aux options de compilation.

Merci à Scott Allen pour les explications.

Sécurité : Cross-Site Request Forgery (CSRF/XSRF)

Le magazine 01informatique vient de sortir un dossier complet sur la sécurité dans les sites AJAX (http://www.01net.com/01informatique/). Ayant entendu le teaser à la radio (les développeurs ont négligé la sécurité !! oh mon dieu), je m'attendais à un article pour pinpins basé sur des vérités générales. Je dois avouer que j'ai été surpris de la qualité avec laquelle le dossier a été traité. Il ont même réussi à me faire peur et j'ai repassé dans ma tête tous les sites web que j'ai pu développer en me posant cette question : sont-ils sécurisés, y'a t-il des failles ?

Je me suis toutefois rassuré rapidement, ASP.NET est loin d'être un gruyère et j'ai toujours appliqué les grands principes de sécurité du Web :

  • vérifier toutes les saisies utilisateurs (Cross-Site Scripting, SQL Injection, Overflow)
  • faire attention à tout Cookie, les crypter si nécessaire
  • limiter la surface d'exposition des API publiques (WS, Ajax, GET, etc.)
  • utiliser des ID temporaires (token ou ticket), casser les séquences (IDENTITY)

Cependant, j'ai été interpellé dans l'article par une technique de piratage appelée : Cross-Site Request Forgery (CSRF ou XSRF pour les intimes). Ne connaissant pas cette technique, j'ai donc fait quelques recherches pour me documenter et je me suis monté un petit lab pour voir comment l'utiliser (et la contourner).

Je ne vais pas décrire dans le détail l'histoire et la description de cette faille donc je laisse la main a des spécialistes pour cette partie : http://www.cgisecurity.com/articles/csrf-faq.shtml

Le principe, c'est qu'une page, un site tiers, un mail, un document, etc. exécute une requête vers le site à pirater en utilisant l'identité "reconnue" de l'utilisateur. Cela paraît simple sur le papier mais bien évidemment il faut réunir de nombreuses circonstances pour réussir son coup. La principale étant que le site à pirater doit baser son authentification sur un cookie persistant.

Démonstration : Sur mon site, j'expose une fonctionnalité. On va prendre la plus simple à détourner, le GET : http://lab.djeepy1.net/CSRF/default.aspx?action=achat&article=123456. Supposons que dans le code behind, je vérifie uniquemement la présence d'un cookie pour sécuriser l'action.

if (Request.Cookies["UserId"] != null)
{
    //on considère que l'utilisateur est OK
    BusinessLayer.ShoppingMgr.Buy(Helper.Decrypt(Request.Cookies["UserId"].Value), Request["ArticleId"]);
}

Le site tiers a juste à faire une requête, mais comment peut-il récupèrer la bonne valeur de cookie ? Avec un Cross-Site Scripting (XSS) classique mais pour cela il doit pouvoir exécuter du Javascript sur votre site (cf. règle n° 1 plus haut). L'autre moyen est à la base du Cross-Site Request Forgery : on s'arrange pour que cette requête soit exécutée depuis le navigateur de l'utilisateur, ainsi, les cookies sont automatiquement envoyés au site à pirater avec la fausse requête. Par exemple, on vous envoie un message sur votre webmail avec une image pointant sur la fonctionnalité GET. Vous ne voyez qu'une image "cassée" et pourtant, l'action a eu lieu sur l'autre site.

<img src="http://site/?action=XX"/>

Pourquoi donc Ajax est-il pointé du doigt ? Cette faille est vieille comme le web (1998) mais très difficilement exploitable dans un monde fondé sur les Postback. Avec Ajax, de nombreuses API s'exposent sans pudeur sur le Web, bien souvent accessibles avec un simple GET.

Comment sécuriser son site ? Vérifiez toute l'API exposée et vérifiez que vous n'utilisez pas de cookie persistant pour authentifier une requête ou tout du moins que TOUTE votre sécurité ne soit pas basée sur cet unique moyen. Les Sessions ASP.NET sont protégées de cette faille car elles se basent sur un cookie dit "de session" (temporaire et lié à la navigation).

Cette faille fait la une car on assiste depuis quelques temps à un nouveau type de site Web : le site composite (ou mashup). Un site composite est un assemblage de modules provenant de différents autres sites. Exemple : mes dernières enchères depuis ebay, mes news depuis MSN, ma météo depuis meteoconsult et mon site de chat depuis googletalk. Tout étant exécuté avec le même contexte, un module peut potentiellement exploiter les fonctionnalités d'un autre (via son API Javascript). 

En conclusion, ne cédez pas à la panique et suivez les conseils suivants (qui s'appliquent même au dela de l'informatique) :

  • quels sont les risques (mobile du crime, conséquences) ?
  • quelles sont les failles (ouvertures, exposition) ?
  • comment je souhaite me protéger de ces risques ?

Pour cela, prévoyez toujours une phase d'audit de votre site web avant la mise en production.

mercredi 30 mai 2007

Afficher un titre sous la forme d'une image en CSS

Tout développeur de site web a été confronté à cette problématique et la réponse technique que l'on rencontre dans 99% des cas est de mettre un tag <img />. Cela répond certes à la problématique mais ce n'est en aucun cas bulletproof[1] et ne respecte pas les rudiments du web design.

En effet, les moteurs de recherche ne considèreront pas comme pertinente l'information, les outils d'accessibilité risquent d'être perdus et votre code HTML peut devenir vite inmaintenable avec cette forme de conception. Comment faire pour palier à ces problématiques ? Un webdesigner vous répondra : CSS biensur ! Et je ferai la même réponse.

Repartons du début :

  1. Toujours commencer par trouver la sémantique correcte. Pour un titre, on met un tag <h1/> (ou h2, etc.)
  2. on met le titre de façon TEXTUELLE pour être compris par les outils donc <h1 id="maintitle">ici mon super titre</h1>
  3. l'image est décorative donc on la met dans le style. Dans votre CSS, vous aurez
    #maintitle { background:url(title.gif) ; }
  4. à ce stade, tout semble correct sauf que le texte apparait au dessus de l'image, il faut donc le faire disparaître. Il existe 2 solutions.
  5. 1ère solution (la plus élégante) : mettre un tag dit technique pour faire disparaître le texte. Le HTML devient <h1 id="maintitle"><span>ici mon super titre<span></h1>.
    Ensuite, il suffit de faire disparaître le <span> en CSS : #maintitle span {visibility:hidden[2] ;}
    Attention : vous aurez certainement des problèmes de padding à régler.
  6. 2ème solution (à proscrire mais bon) : on a pas besoin du span et on décale le texte pour le faire sortir de la fenêtre #maintitle { text-indent:99999px; } 

[1]: j'adore ce terme et vous le trouverez souvent dans mes propos (au même titre que le très fameux : "ça poutre")
[2]: on peut utiliser un display:none mais la taille du rendu du h1 devient minuscule (voire invisible); visibility:hidden permet de garde la taille que le texte aurait pris