Accueil > .NET, Web > ASP.net MVC – Les filtres d’action

ASP.net MVC – Les filtres d’action

Les filtres d’action fournissent un mécanisme efficace pour attacher aux actions et/ou aux contrôleurs asp.net MVC  du code qui ne leur est pas spécifique selon la philosophie de programmation orientée aspect.

En effet, Asp.net MVC permet grâce à sa syntaxe d’attributs de décorer les actions de contrôleurs avec des modules de « métadonnées exécutables », applicables à différents moments clefs d’une action de contrôleur. Ce système permet au développeur de retirer des actions de contrôleur tout le code qu’il ne désire pas y voir dupliqué : la gestion des logs, du cache, des autorisations, des paramètres http… Assurant ainsi une bonne séparation des responsabilités et minimisant l’effet « code tangling ».

Les filtres d’action héritent de la classe abstraite System.Net.Mvc.ActionFilterAttribute, donc de la puissance de la notion d’attribut du framework.

Je vais présenter ici les aspects principaux de cette fonctionnalité du framework puis un exemple concret d’utilisation de filtres personnalisés.

Les filtres prédéfinis

Il existe plusieurs filtres d’action prédéfinis dans le framework :

  • AuthorizeAttribute permet de restreindre l’accès à certains utilisateurs ou rôles.
  • OutputCacheAttribute permet le paramétrage de la mise en cache du résultat des actions de controleurs.
  • HandleErrorAttribute permet le paramétrage des gestions d’exception au sein des actions de controleurs.

Voici un exemple d’utilisation des attributs HandleError et Authorize précablés :

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.WelcomeMessage = "Bienvenue chez Novedia !";
        return View();
    }
 [Authorize]
    public ActionResult AuthenticatedUsers()
    {
        ViewBag.WelcomeMessage = "Vous êtes authentifié.";
        return View();
    }
 [Authorize(Roles = "Admin, Super User")]
    public ActionResult AdministratorsOnly()
    {
        ViewBag.WelcomeMessage = "Bonjour, maître.";
        return View();
    }
 [Authorize(Users = "Simon, John")]
    public ActionResult SpecificUserOnly()
    {
        ViewBag.WelcomeMessage = string.Format("Salut {0} !", User.Identity.Name);
        return View();
    }
}

On voit qu’il est possible de décorer un contrôleur entier, ce qui permettra l’exécution du filtre pour toutes les actions qu’il contient. Ce mode d’utilisation correspond bien à la gestion des erreurs par nature transverse au sein de l’application. Note : il peut être pertinent d’appliquer ses filtres de façon externe au contrôleur (fichier XML) mais cela sort du cadre de cet article : voir la bibliographie.

Les paramètres nommés

Les lecteurs attentifs ont pu remarquer grâce à l’exemple précédent que l’on peut passer des paramètres à l’instanciation des attributs via les propriétés publiques qu’ils exposent :

[Authorize(Roles = "Admin, Super User")]
public ActionResult AdministratorsOnly()
{
    ViewBag.WelcomeMessage = "Bonjour, maître.";
    return View();
}

Héritant du comportement des attributs, l’appel au constructeur par défaut permet de passer des valeurs nommées. C’est la notion de Named Parameters, donnant à l’instanciation des filtres une très grande lisibilité. 

Si l’on désire donner davantage de finesse ou de de complexité aux filtres existants, il faudra les étendre (cf. Extension d’un filtre existant).

Configuration des filtres

Il est possible d’appliquer un filtre sur toutes les actions de l’application, on parle alors de filtre global. Par défaut lors de la creation d’un projet web, Visual Studio applique ainsi le filtre HandleError partout via la classe FilterConfig de la façon suivante (rendant inutile l’attribut HandleError décorant mon HomeController ci-dessus) :

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}

Puis dans la méthode Application_Start de notre global.asax :

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
}

Ordre de filtrage

Lorsque plusieurs filtres sont appliqués à une action, il est prévu de pouvoir spécifier un ordre d’exécution des filtres à l’aide du paramètre nommé Order (extrait d’un exemple détaillé dans la suite de l’article) :

[CheckSession(Order = 1, ActionName ="Question"), CheckQuizValid(Order = 2)]
public ActionResult QuizOk(int questionIndex1Based)
{
    ViewBag.FinalMessage = "Bravo, vous avez correctement répondu au quiz !";
    return View();
}

Extension d’un filtre existant

Démonstration par l’exemple : nous voudrions obtenir des logs d’erreurs de façon automatique sur toutes les actions de notre application sans redéfinir la logique de gestion d’erreur. Pour cela, créons une classe HandleAndLogErrorAttribute héritant de l’attribut HandleErrorAttribute et utilisant la librairie log4net :

public class HandleAndLogErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        ILog log = LogManager.GetLogger("HandleAndLogErrorHandler");
        log.Error(filterContext.Exception.Message);
        base.OnException(filterContext);
    }
}

Ce filtre peut être appliqué aux actions/contrôleurs comme montré précédemment, mais étant donné qu’on voudra le voir appliqué dans toute l’application, on préférera l’enregistrer en tant que filtre global en tant que remplaçant du filtre par défaut HandleError :

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleAndLogErrorAttribute());
    }
}

C’est tout ! Toutes les actions provoquant une erreur non gérée remonteront désormais une entrée de log via log4net.

Création d’un filtre personnalisé

Le framework .net permet de s’adapter rapidement aux problématiques spécifiques d’un projet web en créant ses propres filtres.

Les différentes possibilités des filtres d’action

La classe ActionFilterAttribute nous permet de redéfinir 4 méthodes dont l’appel se fait à différents moments de l’éxécution de l’action que le filtre décore.

  • OnActionExecuting : avant l’éxécution du code de l’action
  • OnActionExecuted : après l’éxécution du code de l’action
  • OnResultExecuting : avant l’éxécution du résultat de l’action (ActionResult)
  • OnResultExecuted : après l’éxécution du résultat de l’action

Exemple : l’authentification et le contrôle de session « Maison »

Par exemple, imaginons que nous devions créer un site de jeu quiz dont le premier accès ne peut se faire que via une page d’accueil attendant un certain jeu de données http post – données envoyées par un site Mère qui connait le client et sait quelles données nous envoyer. Après récupération de ces données, nous sauvegardons ce client en session. Plus tard, le client devra être retrouvé en session à chacune de ses requêtes pour pouvoir naviguer dans le quiz. Il y aura donc deux cas à différencier :

  • Première visite : un filtre à usage unique récupérant les données post envoyées sur l’action de la page d’accueil et sauvegardant l’utilisateur en session
  • Visites ultérieures : un filtre utilisé sur toutes les autres pages du quiz vérifiant cette variable de session et le cas échéant redirigeant le client sur le site Mère

De façon assez intuitive, on va créer un filtre par cas.

Créons donc tout d’abord notre premier filtre PostDataFilter.

public class PostDataFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Client client = new Client
        {
            Id = filterContext.HttpContext.Request.Params["client_id"],
            Email = filterContext.HttpContext.Request.Params["client_email"],
            FirstName = filterContext.HttpContext.Request.Params["client_firstname"]
        };
        filterContext.HttpContext.Session["Client"] = client;
        base.OnActionExecuting(filterContext);
    }
}

Avant l’éxécution de l’action (OnActionExecuting), comme convenu on récupère les données post dans le contexte http, on instancie un client et on le met en session.

Créons ensuite notre second filtre CheckSession :

public class CheckSession : ActionFilterAttribute
{
    public string RedirectUrl { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Session["Client"] == null)
        {
            filterContext.Result = new RedirectResult(RedirectUrl);
        }
        base.OnActionExecuting(filterContext);
    }
}

Ici il s’agit de vérifier la présence en session du client. S’il n’y est pas ou plus, on le redirige en remplaçant le ActionResult de l’action à la volée par un RedirectResult dont le paramètre est renseigné par la propriété publique correspondante RedirectUrl en vue de l’utiliser comme paramètre nommé.

Application de nos filtres

Ainsi, dans notre contrôleur, l’action d’accueil sera la seule à être décorée avec notre premier filtre :

[PostDataFilter]
public ActionResult Index()
{
    ViewBag.WelcomeMessage = "Bienvenue chez Novedia !";
    return View();
}

Et toutes les autres actions pour lesquelles une session Client doit exister seront elles décorées avec notre second filtre :

[CheckSession(RedirectUrl="~/Home/EspaceClient")]
public ActionResult Question(int questionIndex1Based)
{
    QuestionModel model = new QuestionModel(questionIndex1Based);
    return View(model);
}

 

[CheckSession(Order = 1, RedirectUrl = "~/Home/EspaceClient"), CheckQuizValid(Order = 2)]
public ActionResult QuizOk(int questionIndex1Based)
{
    ViewBag.FinalMessage = "Bravo, vous avez correctement répondu au quiz !";
    return View();
}

On remarque deux choses :

  • La propriété automatique RedirectUrl créée dans le filtre est considérée automatiquement comme un paramètre nommé pour instancier notre filtre, ce qui nous économise l’écriture d’une surcharge de constructeur dédiée.
  • Pour l’action QuizOk necessitant l’application de deux filtres, l’utilisation du paramètres nommé Order nous permet de valider d’abord l’existence de notre joueur en session avant de vérifier l’état de son quiz.

Le mot de la fin

Les filtres d’action s’intègrent donc parfaitement dans la logique du développeur ASP.net, en lui permettant comme on l’a vu avec notre authentification « Maison » de concentrer la logique métier du contrôleur dans le code de ses actions et de décorreler les traitements par nature « transverses », le tout rapidement et en gardant une grande lisibilité.

Bibliographie

Filtrage d’actions dans les applications ASP.NET MVC

Creating Custom Actions Filters in ASP.NET MVC (en anglais)

Dynamic Action Filters in ASP.NET MVC (en vue de l’externalisation des filtres par rapport au contrôleur, en anglais)

  1. Pas encore de commentaire
  1. Pas encore de trackbacks


9 × = dix-huit