• 30 hours
  • Medium

Free online content available in this course.

course.header.alt.is_certifying

You can get support and mentoring from a private teacher via videoconference on this course.

Got it!

Last updated on 11/23/17

Les routes

Log in or subscribe for free to enjoy all this course has to offer!

Après cette petite introduction qui nous a mis en appétit, il est temps de plonger dans du complexe, du sérieux, du lourd, du tatoué, du méchant ! Mais rassurez-vous, complexe ne veut pas dire compliqué. Nous allons continuer à y aller tranquillement et vous verrez, vous allez tout comprendre sans problèmes, j’en suis sûr. ;) 

Nous allons donc voir plus en détail tout ce que nous avons entraperçu juste avant. Il était difficile d’expliquer chaque élément de MVC en détail sans avoir vu un peu tout ce qui le compose. Parce que dès qu’on parle de vue, on doit parler de modèle ou de contrôleur. Dès qu’on parle de contrôleur, il est tout de suite question de vue et de modèle… Bref, tout étant un peu lié, il vaut mieux avoir déjà une vague idée de ce que chaque élément est, afin de ne pas être trop perdu dans les explications.

Attaquons tout de suite avec les routes que nous n’avons que trop peu expliquées… Oui c'est vrai, nous avons déjà parlé des routes dans notre étude de l’Hello World. Nous avons dit que la route permettait de transformer une URL en une action d’un contrôleur. C’est vrai ! Nous l’avons même vérifié ! Mais c’est un peu incomplet. Les routes permettent de faire plus que ça et avec des subtilités. 

Voyons comment cela fonctionne et comme toujours, le mieux est de mettre les mains dans le cambouis.

Le routing

Ce nom barbare - c’est de l’anglais bien sûr… on peut le traduire par « routage » - consiste à aiguiller les requêtes des visiteurs dans notre application ASP. NET MVC. Et c’est bien ça que nous souhaitons faire, réagir correctement et potentiellement différemment en fonction de ce que souhaite faire l’utilisateur avec ses URL.
Nous avons vu la forme de routage la plus simple, qui a été ajoutée par défaut par ASP.NET MVC. Celle-ci instancie un contrôleur et exécute une action à partir de l’analyse de cette l’URL. Revenons sur cette route par défaut.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Nous y trouvons deux choses. Il y a l’appel à la méthodeIgnoreRoute , qui vous l’aurez compris, permet d’ignorer les requêtes qui ont la forme /n-importe-quoi.axd/n-importe-quoi.

En fait, le routing ignore les appels aux ressources dont l’extension est axd, et peu importe s'il y a quelque chose à la suite. Les requêtes ayant pour extension axd constituent des requêtes aux HTTP handlers d’ASP.NET. Nous n’allons pas parler des handlers HTTP, sachez simplement qu’il s’agit d’une classe qui est exécutée en amont de tout traitement par le site web, avant d’afficher une page. Cela peut servir notamment à obtenir des ressources particulières, comme des fichiers CSS ou JavaScript mais aussi pourquoi pas à générer dynamiquement une image.
Ici en l’occurrence, le but est d’ignorer ces éventuelles requêtes, afin qu’elles ne soient pas traitées par nos contrôleurs, et qu’elles suivent leur cycle de vie propre.
Laissons ceci de côté et passons à la ligne suivante qui va nous intéresser particulièrement :

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Comme nous l’avons déjà vu, cette route, nommée «Default  », permet de réagir à des requêtes de type /xxx/yyy/zzz. Ces requêtes vont instancier le contrôleurxxxController , et appeler la méthodeyyy() en lui passant en paramètre la valeurzzz . Ainsi, une requête /Home/Index/123 permettra d’appeler la méthode suivante :

public class HomeController : Controller
{
    public string Index(string id)
    {
        return "HomeController.Index " + id;
    }
}

Ce qui affichera la chaîne HomeController.Index 123.

C’est le schéma de route {controller}/{action}/{id} qui permet de mettre automatiquement la valeur Home  dans la propriétécontroller , de mettreIndex  dans la propriétéaction  et de mettre123  dans la propriétéid .
Vous avez vu également qu’il est possible d’avoir des valeurs par défaut, grâce à la propriétédefaults  qui, si la valeur du contrôleur est absente la remplacera parHome  ; de même pouraction  qui par défaut vaudraIndex . Quant à l’identifiant, il aura la valeur d’un paramètre optionnel, représenté par la classeUrlParameter  qui est une classe (presque) vide.

Ainsi, /Home/Index est équivalent à /Home et est également équivalent à /.

J’en rajoute encore une couche histoire d’être sûr que tout le monde a compris… Que fera donc la route suivante : /Index ?
Elle appellera la méthodeIndex  du contrôleurIndexController… qui n’existe pas ! Eh oui, je suis sûr que vous ne vous êtes pas trompés.Index  est équivalent à/Index/Index . Le paramètrecontroller  vautIndex , l’action n’existe pas, donc le paramètreaction  est affecté à sa valeur par défaut ; de même avec le paramètreid .
Attention, l’identifiantid  est une chaîne de caractère dans notre méthode. Il est possible de faire en sorte que ce soit un entier (ou autre) en changeant le type du paramètre :

public class HomeController : Controller
{
    public string Index(int id)
    {
        return "HomeController.Index " + id;
    }
}

Ainsi, la route suivante http://localhost:49874/Home/Index/123 affichera HomeController.Index 123 mais par contre, la route suivante http://localhost:49874/Home/Index/Nicolas provoquera l’exception suivante :

Le dictionnaire de paramètres contient une entrée Null pour le paramètre « id » de type non Nullable « System.Int32 » pour la méthode « System.String Index(Int32) » dans « DemoRoutes.Controllers.HomeController ». Un paramètre facultatif doit être un type référence, un type Nullable ou être déclaré en tant que paramètre facultatif.
Nom du paramètre : parameters

Cela veut dire qu’ASP.NET MVC a détecté que la chaîne Nicolas n’était pas convertible en entier. Du coup, il a passé le paramètre par défaut (à savoirUrlParameter.Optional ) à la méthodeIndex , qui est incompatible avec unint  non nullable. Pour pouvoir avoir un paramètre entier optionnel, il faut utiliser la syntaxe suivante et utiliser un entier nullable :

public class HomeController : Controller
{
    public string Index(int? id)
    {
        return "HomeController.Index " + id;
    }
}

Cette fois-ci, la route http://localhost:49874/Home/Index/Nicolas ne lèvera plus d’exception, mais ASP.NET MVC sera toujours dans l’impossibilité de transformer la chaîne Nicolas en entier, c’est normal, je ne suis pas un numéroooooooo ! :p
Et c’est bien la valeur optionnelle qui est passée à la méthode etid  vaudra doncnull .

Créer des routes

Bien sûr, ASP.NET MVC dans sa grande mansuétude, nous autorise à créer nos propres routes, voire à modifier la route par défaut si elle ne nous plaît pas. Et oui, imaginons que je souhaite passer plus qu’un paramètre à ma méthode par exemple en naviguant sur l’URL /Home/Index/Nicolas/Delphine. Si vous tentez l’expérience, le mécanisme de routage d’ASP.NET MVC sera incapable de vous trouver le bon aiguillage et vous renverra une erreur 404, qui correspond à l’erreur indiquant l’absence de ressource à l’emplacement demandé.

Par exemple, imaginons que nous souhaitions nous servir de notre application web comme d’une calculatrice et que l’URL suivante /Ajouter/5/11 nous affiche le résultat (hautement inédit) de 16. Il suffit de remplacer la route par :

routes.MapRoute(
    name: "Default",
    url: "{action}/{valeur1}/{valeur2}",
    defaults: new { controller = "Calculateur", action = "Ajouter", valeur1 = 0, valeur2 = 0 });

Bien sûr, vous aurez besoin de créer le contrôleurCalculateurController  et la méthodeAjouter  suivante :

public class CalculateurController : Controller
{
    public string Ajouter(int valeur1, int valeur2)
    {
        int resultat = valeur1 + valeur2;
        return resultat.ToString();
    }
}

Et le tour est joué. Naviguez sur /Ajouter/5/11 et vous aurez le résultat 16.

Et que va renvoyer la route / ?
0 bien sûr, vous avez bien suivi !Action  prend la valeur par défautAjouter ,valeur1  etvaleur2  prennent tous les deux la valeur 0. Et 0+0, ça fait bien zéro, comme l'ancien site du même nom !

Allez, pour le plaisir, rajoutons une petite soustraction dans notre contrôleur :

public class CalculateurController : Controller
{
    public string Ajouter(int valeur1, int valeur2)
    {
        int resultat = valeur1 + valeur2;
        return resultat.ToString();
    }

    public string Soustraire(int valeur1, int valeur2)
    {
        return (valeur1 - valeur2).ToString();
    }
}

Ainsi, la route /Soustraire/10/4 renverra sans surprise 6.

Il n’est bien sûr pas obligatoire de conserver le / pour séparer les fragments d’URL. Vous pouvez utiliser le délimiteur que vous souhaitez à partir du moment où deux fragments ne se retrouvent pas côte à côte, ce qui rendrait leur différenciation impossible. Par exemple, nous pourrions faire une route de cette forme :

routes.MapRoute(
    name: "Default",
    url: "Calculatrice-{action}/{valeur1}-{valeur2}",
    defaults: new { controller = "Calculateur", action = "Ajouter", valeur1 = 0, valeur2 = 0 });

qui bien sûr fonctionnerait avec la route : /Calculatrice-Ajouter/2-4

Mais il est bien sûr impossible de faire ceci :

routes.MapRoute(
    name: "Default",
    url: "{action}{valeur1}-{valeur2}",
    defaults: new { controller = "Calculateur", action = "Ajouter", valeur1 = 0, valeur2 = 0 });

Comment déterminer où s’arrête l’action et où commence la valeur1 ? D’ailleurs ASP.NET MVC nous le fait remarquer avec l’erreur suivante :

Un segment de chemin d'accès ne peut pas contenir deux paramètres consécutifs. Ils doivent être séparés par '/' ou par une chaîne littérale.

Définir plusieurs routes

Il est possible de définir plusieurs routes pour notre application ASP.NET MVC. Imaginons qu’en plus de savoir ajouter des nombres, nous souhaitions que notre application sache instancier une méthode d’un contrôleur comme précédemment. Il suffit d’enchaîner les routes, avec par exemple :

routes.MapRoute(
    name: "Ajouter",
    url: "Ajouter/{valeur1}/{valeur2}",
    defaults: new { controller = "Calculateur", action = "Ajouter" });

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });

Ainsi, si nous naviguons sur l’URL suivante : /Ajouter/4/7 nous aurons bien le résultat 11. Puis si nous naviguons sur /Home/Index/123, nous aurons alors le résultat précédent, à savoir : HomeController.Index 123

Par contre, attention à l’ordre !
L’ordre dans lequel nous ajoutons les définitions des routes est important et détermine l’ordre dans lequel seront testées les différentes routes. Ainsi, dans cet ordre, lorsque nous tentons de naviguer sur /Ajouter/4/7 il va déjà tenter de déterminer la routeAjouter . Elle correspond, on s’arrête là et on appelle la méthodeAjouter  du contrôleurCalculateur .
Lorsque nous naviguons sur /Home/Index/123, nous tentons de déterminer la routeAjouter , qui ne fonctionne pas, donc nous passons ensuite à la routeDefault . Cette fois-ci, ça passe.

Si nous inversons l’ordre des deux routes, sur la requête /Ajouter/4/7, ASP.NET MVC va tenter d’instancier le contrôleurAjouter  qui n’existe pas et ceci va provoquer une erreur qui empêchera la route suivante d’être analysée.

D’une manière générale, il faut toujours commencer par ajouter la route la plus spécifique pour finir par la plus générale.

C'est quand même pratique ces routes multiples, nous allons pouvoir nous en servir dans notre nouvel exemple où nous souhaitons être capables d'afficher la météo d’un jour précis mais également capables de faire d’autres choses. Bref, pouvoir réagir en même temps à des routes de ce genre :

  • jour/mois/annee 

  • controller/action/id 

Parfait, avec les multiples routes, je vais pouvoir définir quelque chose comme ceci :

routes.MapRoute(
    name: "Meteo",
    url: "{jour}/{mois}/{annee}",
    defaults: new { controller = "Meteo", action = "Afficher" });

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });

Avec un contrôleurMeteo  tout fraîchement créé :

public class MeteoController : Controller
{
    public string Afficher(int jour, int mois, int annee)
    {
        return "Il fait soleil le " + jour + "/" + mois + "/" + annee;
    }
}

Eh bien non ! Cela ne fonctionne pas. ASP.NET peut afficher sans problème la route /23/3/2013 mais pas la route /Home/Index/123… Je le sens, vous êtes déçu et vous pouvez l’être… Je vous promets des grandes choses et là, crac, c’est la désillusion !

Les contraintes de route

Heureusement, ASP.NET MVC a plus d’un tour dans son sac et va nous permettre de définir des contraintes sur des éléments de route. Par exemple, l’affichage de la météo d’un jour précis ne peut fonctionner qu’avec des chiffres. Ça n’a bien sûr pas de sens d’afficher la météo de Home/Index…
Il suffit de dire que la routeMeteo  n’accepte que des entiers pour les paramètres jour, mois et année. Et cela se fait grâce à des expressions régulières. Par exemple, pour n’autoriser que les nombres, il suffit d’utiliser l’expression régulière\d+  (le + impose la présence d’au moins un chiffre). Nous pourrons donc définir les contraintes grâce à la propriétéconstraints  :

routes.MapRoute(
    name: "Meteo",
    url: "{jour}/{mois}/{annee}",
    defaults: new { controller = "Meteo", action = "Afficher" },
    constraints: new { jour = @"\d+", mois = @"\d+", annee = @"\d+" });

Ceci aura pour impact de faire en sorte que la route ne soit valable que lorsque les paramètres sont tous des entiers. Si ce n’est pas le cas, ASP.NET MVC saute cette route et passe à la suivante. Ainsi, maintenant /Home/Index/123 ne satisfera plus les conditions de la routeMeteo  et pourra donc aller voir si la route suivante est valable et ainsi instancier notre contrôleurHome  et exécuter la méthode Index  comme précédemment.
Plutôt pas mal non ? Nous sauvons l’honneur ! Ouf.  :-°

Bien sûr, vous pouvez envisager toutes les contraintes que vous souhaitez et que vous permettent les expressions régulières. (Ou ce que vous permet votre savoir sur les expressions régulières qui est un vaste sujet que je ne traiterai pas ici.) On pourrait très bien envisager de durcir un peu la contrainte sur l’année. Après tout, une année, c’est forcément 4 chiffres… Surtout de nos jours… Allez, hop, la contrainte suivante impose exactement 4 chiffres pour l’année :

routes.MapRoute(
    name: "Meteo",
    url: "{jour}/{mois}/{annee}",
    defaults: new { controller = "Meteo", action = "Afficher" },
    constraints: new { jour = @"\d+", mois = @"\d+", annee = @"\d{4}" });

Ainsi, la route /23/3/2013 fonctionnera, alors que la route /23/3/999 ne sera pas bonne. Si vous ne connaissez pas toute la puissance des expressions régulières, je ne peux que vous encourager à suivre un tutoriel sur le sujet (par exemple celui-ci). En tous cas, avec ça, vous serez armés pour que vos URL aient exactement la tête que vous souhaitez qu’elles aient.

Gérer un nombre indéterminé de paramètres

De plus, il est possible de traiter les requêtes qui contiennent un nombre variable de segments. Nous avons vu par exemple qu’avec la route par défaut, une URL de ce genre fonctionnait : /Home/Index/123, instanciant le contrôleurHome  et appelant la méthodeIndex  en lui passant un id  valant 123. Par contre, une URL avec plus de segments ne fonctionne pas, du genre : /Home/Index/123/456.

Il est cependant possible de traiter ce genre d’URL en utilisant l’astérisque (*), combiné au dernier paramètre. Il permet d’indiquer que nous souhaitons intercepter tout ce qui reste. Ajoutez pour cela la route suivante :

routes.MapRoute(
    name: "RouteAttrapeTout",
    url: "{controller}/{action}/{*id}",
    defaults: new { controller = "Accueil", action = "Index", id = UrlParameter.Optional }
);

Avec cela, si vous naviguez sur l’URL /Home/Index/123/456 alors id vaudra « 123/456 ». Il faut bien sûr pour cela que le paramètreid  du contrôleur soit de type chaîne de caractères, car 123/456 ne peut pas être converti en entier. Après, libre à vous de faire ce que vous souhaitez de cette chaîne. :)

Générer les routes

Un des avantages de ce mécanisme de routing est qu’il permet facilement de générer une URL grâce au nom d’un contrôleur et d’une action. Cela nous sera utile par exemple lorsque nous souhaiterons faire des liens entre les pages grâce à la balise<a> .
Ainsi, plutôt que d’écrire directement un lien du genre<a href="/Home/Afficher/123">…</a>  nous pourrons utiliser un élément qui génèrera la bonne URL en se basant sur le mécanisme de routing.

Je ne peux pas vous l’illustrer pour l’instant car nous n’avons pas exploré les vues et surtout les helpers HTML.

Temporairement, et pour satisfaire votre curiosité, je vais vous générer un lien côté contrôleur grâce à une méthode qui fonctionne sur le même principe :

public string Index(string id)
{
    return HtmlHelper.GenerateLink(Request.RequestContext, RouteTable.Routes, "Mon lien", null, "Afficher", "Home", new RouteValueDictionary { { "id", id } }, null);
}

Ainsi, cette méthode renverra la chaîne HTML suivante :

<a href="/Home/Afficher/123">Mon lien</a>

Automatiquement, à partir du nom du contrôleur, de son action et de ses paramètres, ASP.NET MVC a généré une URL cohérente. Cela permet d’éviter d’écrire les URL en dur dans les pages et risquer qu’elles ne renvoient vers rien si nous changeons les règles de routing…
Ici, nous laissons à ASP.NET MVC le soin de gérer les URL pour nous.

Remarquez que si vous tentez de générer une URL vers l’actionIndex  du contrôleurHome , nous aurons le lien suivant :

<a href="/Home">Mon lien</a>

Vous aurez constaté que le mécanisme de routing ne renvoie pas le nom de l’action, vu qu’il est facultatif. Il aurait également pu générer juste un /, ce qui aurait été équivalent.

Rendez-vous dans un prochain chapitre pour voir la génération de routes en action dans les vues. :)

Et l’URL Rewriting ?

URL Routing et URL Rewriting sont souvent confondus par les débutants. Ils ont une particularité commune, à savoir qu’ils permettent d’avoir des URL qui sont plus facilement lisibles par nous autres humains, nous permettant ainsi de nous repérer dans les méandres d’un site web.
L’URL Rewriting est un mécanisme qui permet de transformer une URL en une autre. En général, le but de l’URL Rewriting est d’optimiser le référencement naturel d’une page et fait partie des principes du SEO.

Ainsi, imaginons un site d’e-commerce qui affiche des produits. Il semble tout à fait logique qu’une route soit associée à l’affichage de la fiche produit, en prenant en paramètre l’identifiant du produit. Un truc du genre : /Produits/Affiche/xyz123.
Le principe de l’URL Rewriting est de faire en sorte que lorsqu’on saisit une URL, par exemple /informatique/livres/apprendre-ASP-NET-MVC/pas-cher, elle soit transformée en une autre URL, par exemple /Produits/Affiche/xyz123 permettant bien sûr d’afficher la bonne page. Ainsi, les utilisateurs sauront plus facilement que le lien pointe vers une fiche produit d’un livre pas cher sur ASP.NET MVC. De même, les moteurs de recherche pourront utiliser les mots-clés de l’URL pour répondre de manière pertinente à une recherche sur un livre pas cher sur ASP.NET MVC.

L’URL Rewriting est donc à utiliser en complément de l’URL Routing.

En résumé

  • Le routing est un mécanisme arrivant en amont d’une requête HTTP permettant d’aiguiller correctement une action de l’utilisateur vers une méthode de contrôleur.

  • Il est possible de créer ses propres routes et également d’ajouter des contraintes de routes, sous la forme d’expressions régulières.

  • L’URL Rewriting est différent de l’URL Routing et peut s’utiliser en complément.

Example of certificate of achievement
Example of certificate of achievement