Licence CC BY

Le déroulement d'une requête : introduction aux filtres et aux routes

Publié :

En ASP.NET, la notion de filtre et de route est importante pour comprendre comment va fonctionner notre application. Dans le chapitre de l’application BonjourMVC, nous avons utilisé ces deux notions, peut-être sans nous en rendre compte. Pour la suite, il est préférable de commencer à connaître les bases des filtres et des routes.

Les filtres sont des attributs personnalisés qui fournissent un moyen déclaratif pour ajouter un comportement pré et post-action aux méthodes d’action de contrôleur. Les routes, à partir d’une URL, vont déterminer quel contrôleur appeler et avec quels arguments.

Le déroulement d'une requête

Comme nous l’avons vu dans la première partie, lorsque l’utilisateur veut obtenir une page, il envoie une requête au serveur.

Le premier défi du serveur sera de comprendre quel contrôleur vous désirez utiliser et quelle action vous désirez faire. Une action est simplement une méthode d’une classe de contrôleur. Cette étape s’appelle le routage1.

Une fois que le serveur sait quelle action il doit appeler, il va regarder s’il a le droit de l’appeler : la requête va passer par des filtres.

Si les filtres acceptent tous la requête, l’action est appelée. À la fin de celle-ci vous allez générer une page grâce au moteur de template.

D’autres filtres vérifieront que le résultat est bon et si tout va bien, le visiteur verra sa page HTML.

Exécution d'un site

Mais pour l’instant, on n’a jamais parlé de route ou de filtre, alors pourquoi ça marche bien ?

L’avantage de l’architecture MVC, c’est qu’elle est plutôt bien normalisée, du coup, quand vous avez créé votre projet, Visual Studio savait qu’il fallait configurer le routage de manière à ce que toute URL de la forme nom du contrôleur/nom de l'action soit automatiquement bien routée. Souvenez-vous du chapitre précédent avec l’exemple du BonjourMVC, nous avons appelé un contrôleur et chaque méthode de ce contrôleur renvoyait une vue. Au final, nous avions une URL de ce type : /Salutation/Index avec Salutation le nom du contrôleur et Index la méthode.

Visual Studio a donc, pour vous, généré un petit code qui se trouve dans le dossier App_Start, et plus précisément dans le fichier FilterConfig.cs et RouteConfig.cs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class RouteConfig
{
    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 }
        );
    }
}
RouteConfig.cs
1
2
3
4
5
6
7
public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}
FilterConfig.cs

Dans ces deux fichiers, vous aurez les conventions de nommage pour les routes et les filtres qui s’appliquent dans toute votre application.

Et si je veux faire une route spéciale juste à un endroit ?

La partie suivante est faite pour vous.


  1. à ne pas confondre avec le routage réseau, qui n’a rien à voir. 

Les routes par défaut

Revenons sur l’instruction qui nous permet de créer une route :

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

Comme il s’agit d’une route globale, on lui donne un nom : "Default".
La partie intéressante vient avec la partie "url". Il vous dit que par défaut une URL doit être composée :

  • du nom du contrôleur ;
  • du nom de l’action ;
  • d’un id.

{controller} et {action} sont des mots-clefs qui sont immédiatement reconnus par le routeur.

Par contre "id" est un paramètre qui est propre à notre application (UrlParameter).
Dans la ligne qui suit, on vous informe qu’il est optionnel.

Pourquoi ils ont mis "id" s’il est optionnel, et puis à quoi sert-il ?

Souvenez-vous, je vous ai dit dès le départ que la grande force de MVC, c’est que c’était normalisé.
En fait vous faites cinq types d’actions le plus souvent :

  • obtenir une liste ;
  • créer un nouvel objet ;
  • détailler un objet de la liste ;
  • modifier cet objet ;
  • supprimer un objet.

Dans les deux premiers cas, vous avez juste besoin de donner le nom de l’action (lister ou créer). Par contre dans le second cas, vous avez besoin de retrouver un objet en particulier. Le plus souvent, pour cela, vous allez utiliser un identifiant. Pour aller plus vite, on écrit "id".

Prenons un exemple pour notre blog : nos visiteurs voudront avoir la liste de nos articles, puis pourquoi pas ne voir qu’un seul article en particulier. Ce qui équivaudrait à /articles/liste et /articles/detail/5 (pour le 5ème article).

Pour les auteurs d’article, nous avons la même chose du côté de l’édition : /articles/creer et /articles/modifier/5.

Dans notre exemple, l’identifiant, c’est le numéro.

Mais aujourd’hui, il est important d’avoir des URL qui montrent le titre de l’article, comme ça les gens et les moteurs de recherche comprennent mieux de quoi une page parle. On va donc donner un paramètre supplémentaire : le titre adapté (pas de caractères accentués, pas d’espace), on appelle ça un slug.

Si on avait fait une URL par défaut sur le même modèle, elle aurait été : {controller}/{action}/{id}/{slug}1

Et le code aurait donné :

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

Et pour afficher un tutoriel on aurait dû faire une action telle que :

1
2
3
4
5
6
//exemple d'url = /Article/Voir/1/slug 
public ActionResult Voir(int id, string slug)
{
    ViewBag.slug = slug;
    return View();
}

Dans les routes par défaut, le paramètre "Action" est nécessaire.

Comme notre but est de faire un blog, il sera peut être intéressant de donner la possibilité aux gens de nous suivre via un flux RSS. Pour cela nous allons créer notre propre "url par défaut" qui donnera le prefix "flux-rss" et qui redirigera vers RSSFeedController. Nous allons proposer au gens de demander un nombre d’articles à afficher. Par défaut cela sera 5.

Je vous laisse le faire, la correction est masquée.

1
2
3
4
5
routes.MapRoute(
     name: "Flux",
     url: "flux-rss/{action}/{article_number}",
     defaults: new { controller = "RSSFeed", action = "Index", article_number = 5 }
);
Correction

Nous verrons plus tard comment contourner le comportement "par défaut". Cela n’est pas nécessaire pour construire notre premier site.


  1. Le mot "slug" est utilisé pour décrire une URL "lisible" telle que celle de ce tutoriel. Nous verrons plus tard comment en créer un. 

Les filtres

À plusieurs étapes du rendu, vous pouvez vous rendre compte que des filtres sont placés : ils sont faciles à reconnaître, car ils sont placés au dessus des méthodes, commençant par ’[’ et finissant par ’]’. Un exemple que nous avons déjà vu :

1
[HttpPost]

Ces filtres ont pour but d’automatiser certains traitements et en cas d’échec de ces derniers, ils redirigent l’utilisateur vers une page d’erreur.

Les filtres sont des attributs qui héritent de FilterAttribute. Comme ce sont des attributs, pour spécifier un filtre, il faut les placer au-dessus du nom de la méthode ou de la classe.

Lorsque vous spécifiez un filtre sur la classe, il s’appliquera à toutes les méthodes.

La plupart du temps, vous utiliserez les filtres [RequireRole], [Authorize], [AllowAnonymous].

Ces filtres ont la particularité de gérer les cas d’autorisation. Si Authorize ou RequireRole échouent, ils vous envoient une page avec pour erreur "403: not authorized".

Il est fortement conseillé de mettre [Authorize] sur toutes les classes de contrôleur puis de spécifier les méthodes qui sont accessibles publiquement à l’aide de [AllowAnonymous].

Prenons l’exemple de notre blog, créons un contrôleur dont le but est de gérer les articles.

Une bonne pratique pour bien sécuriser votre blog sera :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace mvc_vide.Controllers
{
    [Authorize]
    public class ArticleController : Controller
    {

        // GET: Article
        [AllowAnonymous]
        public ActionResult Index()
        {
            return View();
        }

        // GET: Article/Details/5
        [AllowAnonymous]
        public ActionResult Details(int id)
        {
            return View();
        }

        // GET: Article/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Article/Create
        [HttpPost]
        public ActionResult Create(FormCollection collection)
        {
            try
            {
                // TODO: Add insert logic here

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        // GET: Article/Edit/5
        public ActionResult Edit(int id)
        {
            return View();
        }

        // POST: Article/Edit/5
        [HttpPost]
        public ActionResult Edit(int id, FormCollection collection)
        {
            try
            {
                // TODO: Add update logic here

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        // GET: Article/Delete/5
        public ActionResult Delete(int id)
        {
            return View();
        }

        // POST: Article/Delete/5
        [HttpPost]
        public ActionResult Delete(int id, FormCollection collection)
        {
            try
            {
                // TODO: Add delete logic here

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }
    }
}
Restreindre l’accès par défaut

Vous pouvez, bien entendu, ajouter plusieurs filtres à une même méthode. Dès lors, il est fortement conseillé de spécifier l’ordre d’exécution afin que tout soit prévisible.

Pour faire cela, il suffit de donner un paramètre nommé au filtre. Si vous désirez que l’autorisation soit exécutée en premier, faites : [Authorize(Order:1)].


Ce chapitre est terminé, avec deux notions importantes et assez simples à assimiler :

  • les filtres, qui permettent de spécifier les conditions à respecter au début ou à la fin d’un contrôleur ou d’une action ;
  • les routes, qui quant à elles décrivent les demandes entrantes issues de navigateur via l’URL, pour des actions de contrôleur particulières.

Nous avons beaucoup parlé de contrôleur dans ce chapitre, il serait temps d’en savoir un peu plus sur eux. Et ça tombe bien, car c’est au programme du prochain chapitre !