1.Introduction▲
Vous êtes un développeur Asp.Net utilisant le C# et vous souhaitez avoir des URL agréables à lire et faciles à utiliser ? Ou bien améliorer votre référencement ?
Alors ce tutoriel est pour vous.
Vous apprendrez dans ce cours comment utiliser l'Url-Rewriting en C# pour un site web en asp.net. Vous apprendrez également à écrire un module paramétrable de réécriture d'url dans le web.config. Vous saurez également comment gérer les postbacks avec l'url rewriting.
2.L'Url rewriting, qu'est-ce que c'est ?▲
L'url rewriting (réécriture d'url) est la capacité d'intercepter une url qui n'existe pas physiquement sur le serveur pour la transformer en une url existante pour notre site afin que celle-ci soit plus facile à lire ou à écrire pour les utilisateurs.
Exemple : Imaginons que nous ayons un site web d'e-commerce et que nous pouvions accéder à la fiche technique d'un disque dur de 200 Go qui a pour identifiant : XB45BTO de cette façon :
http://monsite/ficheProduit.aspx?id=XB45BTO
Cette url n'est pas très explicite en soi, il aurait surement été préférable de pouvoir y accéder ainsi :
http://monsite/Produits/Disque-dur/Marque-X-200GO.html
Cela a en effet plusieurs avantages. Dans un premier temps, c'est un confort pour l'utilisateur qui est mieux averti de la fiche produit qu'il va visiter. C'est ensuite plus facile à repérer sur les moteurs de recherche.
Techniquement, une url réécrite permet de masquer le fonctionnement interne du site web. Dans notre exemple, on apprend que le site est fait en asp.net et qu'on passe des identifiants de produit dans l'url.
Avoir une url plus explicite et plus descriptive permet également d'améliorer son référencement.
Cette technique permet aussi d'éviter les liens morts. Un utilisateur peut par exemple avoir bookmarké le lien. En cas de changement d'url (par exemple changement de la structure interne du site), le lien bookmarké renverra vers une page inexistante. L'url rewriting aurait pu nous éviter ce souci.
L'url rewriting c'est donc la capacité d'un site à réécrire l'url :
http://monsite/Produits/Disque-dur/Marque-X-200GO.html
en
http://monsite/ficheProduit.aspx?id=XB45BTO
3.Un exemple d'url rewriting simple▲
Le but est d'intercepter chaque url pour savoir s'il y a besoin de la réécrire pour qu'elle soit comprise par notre application. Nous devons donc être en mesure d'intercepter chaque demande de requête de notre application. C'est un des domaines que peut couvrir le global.asax.
3.1.Rappels sur le global.asax▲
Comme précisé dans la FAQ asp.net, la classe Global est la classe capable de gérer des événements du niveau application. Elle se trouve dans un fichier appelé Global.asax (plus précisément Global.asax.cs pour C#).
Msdn nous dit que l'événement BeginRequest signale la création de toute nouvelle demande donnée. Cet événement est toujours déclenché et constitue systématiquement le premier événement à se produire au cours du traitement d'une demande.
C'est donc dans cet événement que devra intervenir notre réécriture d'url.
Ajoutons un fichier global.asax à notre application et surchargeons cet événement : clic droit sur le projet -> add -> new item -> global application class
Il génère par défaut les événements Application_Start et Application_End. Nous pouvons les supprimer et ajouter l'événement Application_BeginRequest :
protected
void
Application_BeginRequest
(
object
sender,
EventArgs e)
{
}
3.2.Réécriture▲
Toute la mécanique de réécriture d'url d'asp.net repose sur la méthode : Context.RewritePath.
3.3.Exemple▲
Dans cet exemple, nous allons simplement chercher à transformer une url de ce type :
http://monsite/nico.html
en
http://monsite/Default.aspx?name=nico
La page Default.aspx récupérera le paramètre dans l'url et l'affichera dans un label.
Nous allons donc ajouter un label dans le fichier Default.aspx :
<
asp:
Label ID=
"leLabel"
runat=
"server"
/>
Et on aura dans le code-behind le code pour récupérer le paramètre name et l'afficher dans le label :
protected
override
void
OnInit
(
EventArgs e)
{
string
aValue =
Request.
QueryString[
"name"
];
if
(!
string
.
IsNullOrEmpty
(
aValue))
leLabel.
Text =
string
.
Format
(
"Le nom passé est : {0}"
,
aValue);
base
.
OnInit
(
e);
}
Notre réécriture, qui se passe dans l'événement Application_BeginRequest, va consister en :
- vérifier que l'url se termine bien par .html ;
- récupérer ce qu'il y a avant le .html ;
- et réécrire l'url en Default.aspx et lui passer la chaine récupérée en paramètre name.
Soit :
protected
void
Application_BeginRequest
(
object
sender,
EventArgs e)
{
string
urlPath =
Request.
Url.
ToString
(
);
if
(
urlPath.
EndsWith
(
".html"
))
{
int
index =
urlPath.
LastIndexOf
(
'/'
);
string
name =
urlPath.
Substring
(
index +
1
,
urlPath.
LastIndexOf
(
".html"
) -
index -
1
);
Context.
RewritePath
(
string
.
Format
(
"/Default.aspx?name={0}"
,
name));
}
}
Maintenant, quand vous taperez par exemple les URL suivantes :
http://monsite/nico.html
http://monsite/nico-pyright.html
Le site affichera :
Le nom passé est : nico
Le nom passé est : nico-pyright
4.Écrire un module d'url rewriting▲
Pour l'instant, ce qu'on a fait, c'est utiliser l'événement d'application BeginRequest pour faire notre réécriture d'url.
Nous allons désormais écrire une classe module qui va se substituer à la classe d'application en la complétant pour gérer notamment les requêtes entrantes. Cette classe va implémenter l'interface IHttpModule et s'abonner à l'événement BeginRequest
4.1.Rappels sur un module Http▲
Comme décrit dans MSDN, un module HTTP est un assembly appelé sur chaque demande effectuée sur votre application. Les modules HTTP sont appelés dans le cadre du pipeline de demande ASP.NET et ont accès aux événements du cycle de vie de toute la demande. Par conséquent, les modules HTTP vous donnent la possibilité d'examiner les demandes entrantes et d'agir en fonction de la demande.
On a besoin de les déclarer dans le web.config de cette façon, à la section <system.web> :
<httpModules>
<add
name
=
"le nom de mon module"
type
=
"namespace.classe, assembly"
/>
</httpModules>
Comme d'habitude, il s'agit de déclarer notre module en précisant son namespace, sa classe et son assembly.
4.2.Une implémentation de module d'url rewriting▲
Pour écrire un module, il faut implémenter IHttpModule. Dans notre cas, il faudra également s'abonner à BeginRequest. Nous devrons ensuite récupérer le context pour procéder à la réécriture d'url.
Le minimum à écrire pour une telle classe est :
public
class
MonUrlRewriter :
IHttpModule
{
public
void
Init
(
HttpApplication context)
{
context.
BeginRequest +=
context_BeginRequest;
}
private
void
context_BeginRequest
(
object
sender,
EventArgs e)
{
HttpApplication application =
sender as
HttpApplication;
HttpContext context =
(
application ==
null
) ?
null
:
application.
Context;
if
(
context !=
null
)
{
HttpRequest request =
context.
Request;
// ici faire l'url rewriting
}
}
public
void
Dispose
(
)
{
}
}
Pour que cette classe soit prise en compte par notre application, on va la définir dans le web.config à la section <system.web> :
<httpModules>
<add
name
=
"MonUrlRewriter"
type
=
"demoModuleUrlRewriting.MonUrlRewriter, demoModuleUrlRewriting"
/>
</httpModules>
4.3.Exemple d'un module url rewriting▲
pour faire l'équivalent de notre premier exemple, dans la méthode context_BeginRequest, on fera :
private
void
context_BeginRequest
(
object
sender,
EventArgs e)
{
HttpApplication application =
sender as
HttpApplication;
HttpContext context =
(
application ==
null
) ?
null
:
application.
Context;
if
(
context !=
null
)
{
HttpRequest request =
context.
Request;
// ici faire l'url rewriting
string
urlPath =
request.
Url.
ToString
(
);
if
(
urlPath.
EndsWith
(
".html"
))
{
int
index =
urlPath.
LastIndexOf
(
'/'
);
string
name =
urlPath.
Substring
(
index +
1
,
urlPath.
LastIndexOf
(
".html"
) -
index -
1
);
context.
RewritePath
(
string
.
Format
(
"/Default.aspx?name={0}"
,
name));
}
}
}
Une fois le contexte récupéré, il s'agit du même code qui va récupérer la chaine représentant la page html et la passer en paramètre à Default.aspx.
Ainsi, notre application saura à nouveau rediriger une url de ce type :
http://monsite/nico.html
en
http://monsite/Default.aspx?name=nico
D'où, le site affichera :
Le nom passé est : nico
5.Amélioration, utiliser le web.config pour paramétrer le module▲
C'est bien beau tout ça, mais on se rend compte qu'on va vite se retrouver avec une usine à gaz si on a plusieurs réécritures à faire, plus ou moins complexe. On va vite se retrouver avec des if dans tous les sens, démultiplier les index of …
Ça va vite devenir ingérable, difficile à maintenir et augmenter grandement les risques d'erreurs !
Une idée intéressante serait de généraliser le traitement et de déplacer le paramétrage dans le web.config histoire d'avoir juste à écrire un truc du genre dans le web.config pour paramétrer l'url rewriting :
<UrlRewriterConfigurationSection>
<rewriting
map
=
"/home.html"
to
=
"~/Default.aspx"
/>
</UrlRewriterConfigurationSection>
qui permettrait la transformation de l'url home.html en Default.aspx.
Ce qui permettrait de rajouter facilement une autre transformation si besoin, sans rajouter de ligne de code. Par exemple, si j'ai besoin de mapper une url contact.html, j'aurai juste à rajouter (par exemple) :
<rewriting
map
=
"/contact.html"
to
=
"~/Default.aspx?action=contact"
/>
5.1.Créer sa section personnalisée▲
Pour avoir une telle section dans son web.config, il faut créer une section personnalisée. Implémenter une section de configuration personnalisée n'est pas si complexe que ça pourrait en avoir l'air.
Je vous renvoie en préambule à mon tutoriel sur les sections personnalisées pour savoir comment implémenter une telle section.
Dans notre cas, cela pourrait être :
using
System;
using
System.
Configuration;
namespace
demoModuleSectionUrlRewriting
{
public
class
UrlRewriterConfigurationSection :
ConfigurationSection
{
private
static
readonly
ConfigurationPropertyCollection _proprietes;
private
static
readonly
ConfigurationProperty _listes;
static
UrlRewriterConfigurationSection
(
)
{
_listes =
new
ConfigurationProperty
(
""
,
typeof
(
ListesElementCollection),
null
,
ConfigurationPropertyOptions.
IsRequired |
ConfigurationPropertyOptions.
IsDefaultCollection);
_proprietes =
new
ConfigurationPropertyCollection
(
);
_proprietes.
Add
(
_listes);
}
public
ListesElementCollection Listes
{
get
{
return
(
ListesElementCollection)base
[
_listes];
}
}
public
new
ListeElement this
[
string
nom]
{
get
{
return
Listes[
nom];
}
}
protected
override
ConfigurationPropertyCollection Properties
{
get
{
return
_proprietes;
}
}
}
public
class
ListeElement :
ConfigurationElement
{
private
static
readonly
ConfigurationPropertyCollection _proprietes;
private
static
readonly
ConfigurationProperty _map;
private
static
readonly
ConfigurationProperty _to;
static
ListeElement
(
)
{
_map =
new
ConfigurationProperty
(
"map"
,
typeof
(
string
),
null
,
ConfigurationPropertyOptions.
IsKey);
_to =
new
ConfigurationProperty
(
"to"
,
typeof
(
string
),
null
,
ConfigurationPropertyOptions.
IsRequired);
_proprietes =
new
ConfigurationPropertyCollection
(
);
_proprietes.
Add
(
_map);
_proprietes.
Add
(
_to);
}
public
string
Map
{
get
{
return
(
string
)base
[
_map];
}
set
{
base
[
_map]
=
value
;
}
}
public
string
To
{
get
{
return
(
string
)base
[
_to];
}
set
{
base
[
_to]
=
value
;
}
}
protected
override
ConfigurationPropertyCollection Properties
{
get
{
return
_proprietes;
}
}
}
public
class
ListesElementCollection :
ConfigurationElementCollection
{
public
override
ConfigurationElementCollectionType CollectionType
{
get
{
return
ConfigurationElementCollectionType.
BasicMap;
}
}
protected
override
string
ElementName
{
get
{
return
"rewriting"
;
}
}
protected
override
ConfigurationPropertyCollection Properties
{
get
{
return
new
ConfigurationPropertyCollection
(
);
}
}
public
ListeElement this
[
int
index]
{
get
{
return
(
ListeElement)BaseGet
(
index);
}
set
{
if
(
BaseGet
(
index) !=
null
)
{
BaseRemoveAt
(
index);
}
base
.
BaseAdd
(
index,
value
);
}
}
public
new
ListeElement this
[
string
map]
{
get
{
return
(
ListeElement)BaseGet
(
map);
}
}
public
void
Add
(
ListeElement item)
{
base
.
BaseAdd
(
item);
}
public
void
Remove
(
ListeElement item)
{
BaseRemove
(
item);
}
public
void
RemoveAt
(
int
index)
{
BaseRemoveAt
(
index);
}
public
void
Clear
(
)
{
BaseClear
(
);
}
protected
override
ConfigurationElement CreateNewElement
(
)
{
return
new
ListeElement
(
);
}
protected
override
object
GetElementKey
(
ConfigurationElement element)
{
if
(
element !=
null
)
return
((
ListeElement)element).
Map;
else
return
null
;
}
}
}
5.2.Gestion de l'url rewriting simple dans le module▲
Maintenant que la section personnalisée est créée, il faut être capable de faire la réécriture d'url en allant lire notre section personnalisée dans l'événement BeginRequest, une fois le contexte récupéré comme d'habitude :
string
urlAbsolutePath =
request.
Url.
AbsolutePath;
if
(
urlAbsolutePath.
EndsWith
(
".html"
))
{
UrlRewriterConfigurationSection section =
(
UrlRewriterConfigurationSection)ConfigurationManager.
GetSection
(
"UrlRewriterConfigurationSection"
);
foreach
(
ListeElement list in
section.
Listes)
{
if
(
urlAbsolutePath ==
list.
Map)
{
context.
RewritePath
(
list.
To);
break
;
}
}
}
On récupère dans un premier temps l'url depuis le contexte. Et on boucle sur toutes sections. Si l'url courante correspond au mapping défini dans une section, alors on réécrit l'url avec la valeur correspondante de la section.
N'oubliez pas de déclarer la section dans le web.config, à savoir :
<configSections>
<section
name
=
"UrlRewriterConfigurationSection"
type
=
"demoModuleSectionUrlRewriting.UrlRewriterConfigurationSection, demoModuleSectionUrlRewriting"
/>
</configSections>
<UrlRewriterConfigurationSection>
<rewriting
map
=
"/home.html"
to
=
"~/Default.aspx"
/>
</UrlRewriterConfigurationSection>
Ainsi, quand on utilise l'url suivante dans le site web :
http://monsite/home.html
il la transforme en :
http://monsite/Default.aspx
Rajoutons désormais dans le web.config la ligne suivante :
<rewriting
map
=
"/contact.html"
to
=
"~/Default.aspx?action=contact"
/>
et traitons dans Default.aspx l'action contact en affichant dans un label que nous sommes dans la page contact :
protected
override
void
OnInit
(
EventArgs e)
{
string
aValue =
Request.
QueryString[
"action"
];
if
(!
string
.
IsNullOrEmpty
(
aValue) &&
aValue ==
"contact"
)
leLabel.
Text =
"Nous sommes dans la page de contact"
;
base
.
OnInit
(
e);
}
Ainsi, quand on utilise l'url suivante dans le site web :
http://monsite/contact.html
le site nous affiche :
Nous sommes dans la page de contact
5.3.Amélioration, utilisation d'expressions régulières▲
Dans le cas précédent, on fait un simple mapping. Pour améliorer et faire quelque chose d'un peu plus complexe, on peut utiliser la capacité du framework.net à gérer les expressions régulières.
Exemple
Imaginons que j'ai une base de tutoriel qui soit accessible par l'url
~/Default.aspx?tutoriel=urlRewriting
Il serait plus pratique et meilleur pour le référencement d'avoir un lien :
/tutoriel/urlRewriting.html
On pourrait imaginer vouloir paramétrer notre webconfig ainsi :
<rewriting map="/tutoriel/.+.html" to="~/Default.aspx?tutoriel={0}"/>
Pour rappel, l'expression régulière .+ permet d'avoir tout ce qui contient plus d'une lettre.
Voici à quoi pourrait ressembler la partie url rewriting :
string
urlAbsolutePath =
request.
Url.
AbsolutePath;
if
(
urlAbsolutePath.
EndsWith
(
".html"
))
{
UrlRewriterConfigurationSection section =
(
UrlRewriterConfigurationSection)ConfigurationManager.
GetSection
(
"UrlRewriterConfigurationSection"
);
foreach
(
ListeElement list in
section.
Listes)
{
Regex reg =
new
Regex
(
list.
Map);
if
(
reg.
IsMatch
(
urlAbsolutePath))
{
string
urlRewrited =
string
.
Format
(
list.
To,
System.
IO.
Path.
GetFileNameWithoutExtension
(
VirtualPathUtility.
GetFileName
(
request.
Url.
LocalPath)));
context.
RewritePath
(
urlRewrited);
break
;
}
}
}
La méthode isMatch nous permet de traiter uniquement les URL qui répondent aux critères définis.
On extrait ensuite la chaine saisie juste avant le .html (ceci est fait grâce aux méthodes de la classe path, en tant que fichier sans extension).
Et on la remplace dans l'url à mapper, grâce à {0} et à string.format.
Pour simuler l'affichage du tutoriel, dans le OnInit, on fera :
string
aValue =
Request.
QueryString[
"tutoriel"
];
if
(!
string
.
IsNullOrEmpty
(
aValue))
leLabel.
Text =
string
.
Format
(
"Voici le tutoriel {0}"
,
aValue);
base
.
OnInit
(
e);
Ce qui fait que les URL suivantes :
http://monsite/tutoriel/urlRewriting.html
http://monsite/tutoriel/asp.net.html
Afficherons :
Voici le tutoriel urlRewriting
Voici le tutoriel asp.net
5.4.Un peu plus loin avec les expressions régulières▲
Une fonctionnalité bien pratique des expressions régulières est la méthode Replace. Pour rester simple, cette méthode nous permet de remplacer une valeur d'une expression régulière par une autre valeur.
Imaginons que je dispose d'une fonctionnalité de recherche implémentée sur mon site, accessible par :
~/Default.aspx?search=maRecherche
On voudrait avoir une url plus digeste du genre :
/search/maRecherche.html
on va paramétrer notre web.config de la sorte :
<rewriting
map
=
"/search/(.+).html"
to
=
"~/Default.aspx?search=$1"
/>
et traiter le cas de cette manière :
UrlRewriterConfigurationSection section =
(
UrlRewriterConfigurationSection)ConfigurationManager.
GetSection
(
"UrlRewriterConfigurationSection"
);
foreach
(
ListeElement list in
section.
Listes)
{
Regex reg =
new
Regex
(
list.
Map);
if
(
reg.
IsMatch
(
urlAbsolutePath))
{
context.
RewritePath
(
reg.
Replace
(
urlAbsolutePath,
list.
To));
break
;
}
}
On boucle sur tous les éléments de notre section et si l'url matche, alors on remplace la valeur trouvée dans la chaine de destination. Ici, $1 sera remplacé par le contenu de (.+).
Ainsi, pour les URL suivantes :
http://monsite/search/nico.html
http://monsite/search/un truc avec des espaces.html
Le site affichera, pour un traitement dans le OnInit du genre :
string
aValue =
Request.
QueryString[
"search"
];
if
(!
string
.
IsNullOrEmpty
(
aValue))
leLabel.
Text =
string
.
Format
(
"Résultat de la recherche pour {0} : ...."
,
aValue);
Résultat de la recherche pour nico : ....
Résultat de la recherche pour un truc avec des espaces : ....
On peut bien sûr cumuler les paramètres de remplacement…
Les expressions régulières sont tellement puissantes qu'on pourrait imaginer rapidement une petite utilisation :
<rewriting
map
=
"/add/(.+)\+(.+).html"
to
=
"~/Default.aspx?value1=$1&value2=$2"
/>
combiné à un traitement dans Default.aspx de ce genre :
string
value1 =
Request.
QueryString[
"value1"
];
string
value2 =
Request.
QueryString[
"value2"
];
if
(!
string
.
IsNullOrEmpty
(
value1) &&
!
string
.
IsNullOrEmpty
(
value2))
{
int
intValue1;
if
(
int
.
TryParse
(
value1,
out
intValue1))
{
int
intValue2;
if
(
int
.
TryParse
(
value2,
out
intValue2))
leLabel.
Text =
string
.
Format
(
"Résultat de {0} + {1} = {2}"
,
intValue1,
intValue2,
intValue1 +
intValue2);
else
leLabel.
Text =
string
.
Format
(
"Paramètre incorrect, {0} doit être numérique"
,
intValue2);
}
else
leLabel.
Text =
string
.
Format
(
"Paramètre incorrect, {0} doit être numérique"
,
intValue1);
}
Nous aurons donc pour les URL suivantes :
http://monsite/add/15+18.html
http://monsite/add/15+0.html
les affichages suivants :
Résultat de 15 + 18 = 33
Résultat de 15 + 0 = 15
5.5.Finalisation du module▲
Les méthodes présentées ci-dessus pourraient avantageusement être combinées pour couvrir un paramétrage complexe.
Il pourrait d'ailleurs être intéressant d'utiliser des groupes de sections et des sous-groupes pour faciliter le paramétrage.
Exemple pour un web.config de ce genre :
<configSections>
<sectionGroup
name
=
"UrlRewriterConfigurationSection"
>
<section
name
=
"Mapping"
type
=
"urlRewritingAll.UrlRewriterConfigurationSection, urlRewritingAll"
/>
<section
name
=
"RegEx"
type
=
"urlRewritingAll.UrlRewriterConfigurationSection, urlRewritingAll"
/>
</sectionGroup>
</configSections>
<UrlRewriterConfigurationSection>
<Mapping>
<rewriting
map
=
"/home.html"
to
=
"~/Default.aspx"
/>
<rewriting
map
=
"/contact.html"
to
=
"~/Default.aspx?action=contact"
/>
</Mapping>
<RegEx>
<rewriting
map
=
"/search/(.+).html"
to
=
"~/Default.aspx?search=$1"
/>
<rewriting
map
=
"/add/(.+)\+(.+).html"
to
=
"~/Default.aspx?value1=$1&value2=$2"
/>
</RegEx>
</UrlRewriterConfigurationSection>
On pourrait avoir notre module d'url rewriting ainsi :
private
void
context_BeginRequest
(
object
sender,
EventArgs e)
{
HttpApplication application =
sender as
HttpApplication;
HttpContext context =
(
application ==
null
) ?
null
:
application.
Context;
if
(
context !=
null
)
{
HttpRequest request =
context.
Request;
string
urlAbsolutePath =
request.
Url.
AbsolutePath;
if
(
urlAbsolutePath.
EndsWith
(
".html"
))
{
TraiteSectionMapping
(
urlAbsolutePath,
context);
TraiteSectionExpressionReguliere
(
urlAbsolutePath,
context);
}
}
}
private
void
TraiteSectionMapping
(
string
urlAbsolutePath,
HttpContext context)
{
UrlRewriterConfigurationSection section =
(
UrlRewriterConfigurationSection)ConfigurationManager.
GetSection
(
"UrlRewriterConfigurationSection/Mapping"
);
foreach
(
ListeElement list in
section.
Listes)
{
if
(
urlAbsolutePath ==
list.
Map)
{
context.
RewritePath
(
list.
To);
break
;
}
}
}
private
void
TraiteSectionExpressionReguliere
(
string
urlAbsolutePath,
HttpContext context)
{
UrlRewriterConfigurationSection section =
(
UrlRewriterConfigurationSection)ConfigurationManager.
GetSection
(
"UrlRewriterConfigurationSection/RegEx"
);
foreach
(
ListeElement list in
section.
Listes)
{
Regex reg =
new
Regex
(
list.
Map);
if
(
reg.
IsMatch
(
urlAbsolutePath))
{
context.
RewritePath
(
reg.
Replace
(
urlAbsolutePath,
list.
To));
break
;
}
}
}
Notez que l'on récupère les sous-sections avec GetSection et l'url Groupe/SousGroupe. Ensuite, pour chaque section, on boucle sur toutes les valeurs et on applique la règle de transformation pour réécrire l'url, s'il y a concordance.
6.Url rewriting et postback▲
Le problème d'un postback combiné à l'url rewriting c'est qu'il utilise l'attribut action de la balise form d'une page pour afficher la page après l'envoi du formulaire.
Ainsi, une page dont l'url aura été réécrite comme home.html (à la place de Default.aspx) qui enverrait un postback au serveur serait affichée après ce postback avec l'url Default.aspx puisque celle-ci est celle qui est enregistrée dans l'attribut action.
C'est tout à fait fonctionnel dans les cas simples, mais disgracieux pour l'utilisateur qui ne comprend pas d'où vient cette url qu'il ne connait pas, alors qu'il en utilisait une autre au départ.
Nous allons donc tacher de résoudre ce problème, en étudiant deux possibilités qui sont basées sur le même principe, à savoir : la modification du comportement de la balise Form pour ne pas réécrire l'attribut action, comme ça, elle conserverait l'url courante et pourrait afficher l'url correcte lors d'un postback.
6.1.Surcharge du contrôle HtmlForm▲
Une première solution serait d'écrire un contrôle personnalisé qui va surcharger le contrôle HtmlFormet qui filtrera l'attribut action.
Une technique pour le faire est de créer une classe qui dérive de HtmlTextWriter et qui surcharge la méthode WriteAttribute pour filtrer l'attribut action. Nous utiliserons cette classe comme writer de notre HtmlForm.
public
class
Form :
HtmlForm
{
protected
override
void
RenderAttributes
(
HtmlTextWriter writer)
{
NoActionWriter customWriter =
new
NoActionWriter
(
writer);
base
.
RenderAttributes
(
customWriter);
}
private
class
NoActionWriter :
HtmlTextWriter
{
public
NoActionWriter
(
HtmlTextWriter writer) :
base
(
writer.
InnerWriter)
{
}
public
override
void
WriteAttribute
(
string
name,
string
value
,
bool
fEncode)
{
if
(
name !=
"action"
)
base
.
WriteAttribute
(
name,
value
,
fEncode);
}
}
}
Pour utiliser ce contrôle personnalisé, dans notre page aspx il faudra enregistrer le contrôle de cette façon :
<%
@ Register TagPrefix=
"MaForm"
Namespace=
"demoUrlRewritingPostBack"
Assembly=
"demoUrlRewritingPostBack"
%>
Et entourer nos contrôles dans la page par la nouvelle balise Form (tout en ayant supprimé l'ancienne balise <form>) :
<
MaForm:
Form runat=
"server"
>
<
a href=
"/home.html"
>
Aller sur /
home.
html</
a>
<
div>
<
asp:
TextBox ID=
"TextBox1"
runat=
"server"
/>
<
asp:
Button ID=
"Button1"
runat=
"server"
Text=
"Envoyer"
/>
<
asp:
Label ID=
"leLabel"
runat=
"server"
/>
</
div>
</
MaForm:
Form>
Cette solution montre dès à présent un inconvénient. Il faut remplacer toutes les balises form de notre application par notre contrôle personnalisé. Ce qui peut être très laborieux si la solution comporte beaucoup de pages, sauf si l'on dispose d'une master page, dans ce cas, il ne faudra le faire qu'une unique fois.
6.2.Utiliser un contrôle adapté▲
Une autre solution qui ne fonctionne qu'avec asp.net 2.0 est d'utiliser un fichier de définition de navigateur pour personnaliser le rendu de la balise form. (Plus de précisions sur les fichiers de définition de navigateur dans MSDN).
Ajouter le dossier spécial App_Browsers : clic droit sur le projet -> add -> add asp.net folder -> App_Browsers.
Et ajouter un nouveau fichier BrowserFile que nous allons appeler FormUrlRewriting.browser
Dedans, nous allons y mettre le type de l'adapter que nous allons utiliser pour le contrôle System.Web.UI.HtmlControls.HtmlForm.
<browsers>
<browser
refID
=
"Default"
>
<controlAdapters>
<adapter
controlType
=
"System.Web.UI.HtmlControls.HtmlForm"
adapterType
=
"demoUrlRewritingPostBackAdapter.FormAdapter, demoUrlRewritingPostBackAdapter"
/>
</controlAdapters>
</browser>
</browsers>
L'attribut refID est utilisé pour associer de nouvelles fonctions à une définition de navigateur existante.
Il ne nous reste plus qu'à créer la classe FormAdapter qui devra hériter de System.Web.UI.Adapters.ControlAdapter. Cette classe permet de personnaliser le rendu pour le contrôle dérivé auquel l'adaptateur est joint, afin de modifier le balisage par défaut ou le comportement de navigateurs spécifiques, et constitue la classe de base à partir de laquelle sont hérités tous les adaptateurs de contrôles.
public
class
FormAdapter :
ControlAdapter
{
protected
override
void
Render
(
HtmlTextWriter writer)
{
NoActionWriter customWriter =
new
NoActionWriter
(
writer);
base
.
Render
(
customWriter);
}
private
class
NoActionWriter :
HtmlTextWriter
{
public
NoActionWriter
(
HtmlTextWriter writer):
base
(
writer.
InnerWriter)
{
}
public
override
void
WriteAttribute
(
string
name,
string
value
,
bool
fEncode)
{
if
(
name !=
"action"
)
base
.
WriteAttribute
(
name,
value
,
fEncode);
}
}
}
Le principe est de surcharger la méthode Render et d'utiliser un writer personnalisé qui n'affiche pas l'attribut, de la même façon que dans le paragraphe précédent.
Ainsi, grâce à cette méthode, nous n'avons plus à remplacer toutes les balises form de notre application.
7.Une méthode de pseudoréécriture d'url, utilisation de la propriété PathInfo▲
Il existe une autre méthode très simple, que je n'apprécie pas spécialement, car je trouve qu'elle dénature l'url. Ce n'est pas une technique de réécriture d'url à proprement parler, mais elle peut valoir le coup d'être connue.
Il s'agit d'utiliser la propriété PathInfo de l'objet Request. Cela permet de connaitre un paramètre d'url qui serait passé sous cette forme :
http://monsite/Default.aspx/nico-pyright
Et qu'on récupérerait comme ça :
string
param =
Request.
PathInfo;
if
(!
string
.
IsNullOrEmpty
(
param))
leLabel.
Text =
string
.
Format
(
"Paramètre passé : {0}"
,
param);
8.Configurer IIS pour l'url mapping▲
Si vous utilisez le serveur web inclus dans visual studio 2005 (Webdev.Webserver), alors vous avez pu tester ces méthodes d'url mapping sans problèmes.
Par contre, si vous avez essayé de le faire avec IIS, vous allez me dire que rien ne fonctionne. Et vous aurez raison.
Il faut en effet configurer IIS pour qu'il puisse interpréter les pages réécrites (qui ont ici une extension .html) comme des pages aspx.
Il faut créer un mapping pour que les fichiers d'extensions .html soient passés dans l'extension ISAPI d'asp.net.
N. B. Une fois l'installation d'IIS terminée, n'oubliez pas d'exécuter la commande suivante pour enregistrer les extensions asp.net :
aspnet_regiis -i
Aspnet_regiis.exe fait partie du framework.net et est situé dans le répertoire du framework utilisé. (chez moi : C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727).
Ensuite, ouvrez le panneau de configuration d'IIS (panneau de configuration -> outils d'administration -> service Internet (IIS)).
Si vous n'avez pas encore créé de site web, faites-le.
Affichez les Propriétés de votre site web et cliquez sur configuration dans l'onglet Répertoire virtuel :
Dans l'onglet Mappages, cliquez sur Ajouter
Dans Exécutable, allez chercher la dll aspnet_isapi.dll (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727).
Saisissez l'extension .html, pour tous les verbes.
La checkbox Moteur de script doit être cochée, celle pour vérifier l'existence du fichier doit être décochée.
Votre IIS est désormais configuré pour interpréter les pages .html comme des pages aspx.
9.Conclusion▲
Voilà pour cet article. Nous avons donc vu le principe de l'url rewriting et avons développé un exemple de module paramétrable par le web.config. Nous avons également étudié deux solutions pour faire cohabiter l'url-rewriting et les postbacks. Nous avons aussi constaté la puissance des expressions régulières qui peuvent nous être d'un grand secours dans les situations de réécriture d'url.
J'espère que cet article vous aura aidé à construire votre propre solution d'url rewriting. Notez qu'il est très simple d'enrichir la section personnalisée du web.config pour l'étendre à vos besoins.
Une erreur courante associée à la réécriture d'url est qu'il peut arriver qu'on « perde » les images ou les références javascript/css. Cela arrive quand on utilise des URL relatives et que la réécriture ne correspond pas à ce qui est attendu. N'hésitez pas à utiliser la syntaxe ~ pour faire référence à la racine de l'application.
Il faut également faire attention quand on utilise le postback et des répertoires réécrit différents de ceux où sont stockées les pages aspx. En effet, celles-ci pourraient ne pas être retrouvées à cause de la réécriture d'un répertoire qui n'existe pas.
Pour palier cet inconvénient, au lieu d'utiliser une comparaison de chaine d'url avec un ==, on utilisera plutôt un Contains ou une expression régulière.
Remerciements▲
Je remercie l'équipe Dotnet pour ses relectures attentives du document.
Contact▲
Si vous constatez une erreur dans le tutoriel, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.