Developpez.com

Plus de 2 000 forums
et jusqu'à 5 000 nouveaux messages par jour

Tutoriel : Ecrire un module d'Url Rewriting pour asp.net en C#

Cet article a pour but de présenter les bases d'écriture d'un module d'url-rewriting.

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1.Introduction

Vous êtes un développeur Asp.Net utilisant le C# et vous souhaitez avoir des urls agréables à lire et facile à 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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

 
Sélectionnez
http://monsite/Produits/Disque-dur/Marque-X-200GO.html

en

 
Sélectionnez
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 : click droit sur le projet -> add -> new item -> global application class

Image non disponible

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 :

global.asax.cs
Sélectionnez
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 :

 
Sélectionnez
http://monsite/nico.html

en

 
Sélectionnez
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 :

Default.aspx
Sélectionnez
<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 :

Default.aspx.cs
Sélectionnez
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 :

global.asax.cs
Sélectionnez
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 urls suivantes :

url saisie dans le navigateur
Sélectionnez
http://monsite/nico.html
url saisie dans le navigateur
Sélectionnez
http://monsite/nico-pyright.html

Le site affichera :

affichée par la page
Sélectionnez
Le nom passé est : nico
affichée par la page
Sélectionnez
Le nom passé est : nico-pyright

Télécharger le source exemple (12 ko).

4.Ecrire 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> :

web.config
Sélectionnez
<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 :

MonUrlRewriter.cs
Sélectionnez
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> :

web.config
Sélectionnez
<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 :

MonUrlRewriter.cs
Sélectionnez
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 :

 
Sélectionnez
http://monsite/nico.html

en

 
Sélectionnez
http://monsite/Default.aspx?name=nico

D'où, le site affichera :

 
Sélectionnez
Le nom passé est : nico

Télécharger le source exemple (12 ko).

5.Amélioration, utiliser le web.config pour paramétrer le module

C'est bien beau tout ca, mais on se rend compte qu'on va vite se retrouver avec une usine à gaz si on a plusieurs réécriture à faire, plus ou moins complexe. On va vite se retrouver avec des if dans tous les sens, démultiplier les index of ...
Ca 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 :

web.config
Sélectionnez
<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) :

web.config
Sélectionnez
<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 ca pourrait en avoir l'air.
Je vous renvoi en préambule à mon tutoriel sur les sections personnalisées pour savoir comment implémenter une telle section.

Dans notre cas, cela pourrait être :

fichiers ListeElement.cs, ListesElementCollection.cs et UrlRewriterConfigurationSection.cs
Sélectionnez
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 :

MonUrlRewriter.cs
Sélectionnez
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 :

web.config
Sélectionnez
<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 :

 
Sélectionnez
http://monsite/home.html

il la transforme en :

 
Sélectionnez
http://monsite/Default.aspx

Rajoutons désormais dans le web.config la ligne suivante :

web.config
Sélectionnez
<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 :

Default.aspx.cs
Sélectionnez
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 :

 
Sélectionnez
http://monsite/contact.html

le site nous affiche :

 
Sélectionnez
Nous sommes dans la page de contact

Télécharger le source exemple (18 ko).

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

 
Sélectionnez
~/Default.aspx?tutoriel=urlRewriting

Il serait plus pratique et meilleur pour le référencement d'avoir un lien :

 
Sélectionnez
/tutoriel/urlRewriting.html

On pourrait imaginer vouloir paramétrer notre webconfig ainsi :

web.config
Sélectionnez
<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 :

MonUrlRewriter.cs
Sélectionnez
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 urls 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 :

Default.aspx.cs
Sélectionnez
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 urls suivantes :

 
Sélectionnez
http://monsite/tutoriel/urlRewriting.html
http://monsite/tutoriel/asp.net.html
				

Afficherons :

 
Sélectionnez
Voici le tutoriel urlRewriting
Voici le tutoriel asp.net

Télécharger le source exemple (17 ko).

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 :

 
Sélectionnez
~/Default.aspx?search=maRecherche

On voudrait avoir une url plus digeste du genre :

 
Sélectionnez
/search/maRecherche.html

on va paramétrer notre web.config de la sorte :

web.config
Sélectionnez
<rewriting map="/search/(.+).html" to="~/Default.aspx?search=$1"/>

et traiter le cas de cette manière :

MonUrlRewriter.cs
Sélectionnez
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 urls suivantes :

 
Sélectionnez
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 :

Default.aspx.cs
Sélectionnez
string aValue = Request.QueryString["search"];
if (!string.IsNullOrEmpty(aValue))
	leLabel.Text = string.Format("Résultat de la recherche pour {0} : ....", aValue);
Affichage de la page
Sélectionnez
Résultat de la recherche pour nico : .... 
Résultat de la recherche pour un truc avec des espaces : .... 

On peut bien sur cumuler les paramètres de remplacement...

Les expressions régulières sont tellement puissantes qu'on pourrait imaginer rapidement une petite utilisation :

web.config
Sélectionnez
<rewriting map="/add/(.+)\+(.+).html" to="~/Default.aspx?value1=$1&amp;value2=$2" />

combiné à un traitement dans Default.aspx de ce genre :

Default.aspx.cs
Sélectionnez
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 urls suivantes :

 
Sélectionnez
http://monsite/add/15+18.html
http://monsite/add/15+0.html
				

les affichages suivants :

 
Sélectionnez
Résultat de 15 + 18 = 33 
Résultat de 15 + 0 = 15 

Télécharger le source exemple (18 ko).

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 :

web.config
Sélectionnez
<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&amp;value2=$2" />
	</RegEx>
</UrlRewriterConfigurationSection>

On pourrait avoir notre module d'url rewriting ainsi :

MonUrlRewriter.cs
Sélectionnez
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.

Télécharger le source exemple (18 ko).

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 ca, 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.

Form.cs
Sélectionnez
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 :

Default.aspx
Sélectionnez
<%@ 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>):

Default.aspx
Sélectionnez
<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.

Télécharger le source exemple (20 ko).

6.2.Utiliser un contrôle adapter

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 : click droit sur le projet -> add -> add asp.net folder -> App_Browsers

Et ajouter un nouveau fichier BrowserFile que nous allons appeler FormUrlRewriting.browser

Image non disponible

Dedans, nous allons y mettre le type de l'adapter que nous allons utiliser pour le contrôle System.Web.UI.HtmlControls.HtmlForm.

FormUrlRewriting.browser
Sélectionnez
<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.

FormAdapter.cs
Sélectionnez
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.

Télécharger le source exemple (21 ko).

7.Une méthode de pseudo-réé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 :

 
Sélectionnez
http://monsite/Default.aspx/nico-pyright

Et qu'on récupérerait comme ca :

Default.aspx.cs
Sélectionnez
string param = Request.PathInfo;
if (!string.IsNullOrEmpty(param))
	leLabel.Text = string.Format("Paramètre passé : {0}", param);

Télécharger le source exemple (10 ko).

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.
NB : Une fois l'installation d'IIS terminée, n'oubliez pas d'exécuter la commande suivante pour enregistrer les extensions asp.net :

 
Sélectionnez

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 :

Image non disponible

Dans l'onglet Mappages, cliquez sur Ajouter

Image non disponible

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.

Image non disponible

Votre IIS est désormais configuré pour interpreter 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 urls 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 leurs relectures attentives du document.

Contact

Si vous constatez une erreur dans le tutorial, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.