Loupe

[ASP.NET MVC 5] A la découverte du routing par attribut

L’une des principales nouveautés d’ASP.NET MVC 5 est la possibilité d’utiliser des attributs de routing directement sur les actions des contrôleurs ou sur les contrôleurs eux même afin de configurer la table de route de l’application. La première question qui se pose est de savoir pourquoi l’on souhaiterait utiliser des attributs et ne pas continuer à utiliser une initialisation classique en ajoutant des routes au démarrage de l’application ? Tout d’abord, il faut savoir que l’un n’empêche pas l’autre : il est tout à fait possible de développer une application qui fait du routing par attribut et par configuration (après, il faut que cela reste cohérent, mais techniquement c’est possible).

L’avantage de passer par des attributs, est que ceux-ci sont directement posés sur les contrôleurs ou les actions, du coup d’un simple coup d’oeil, on est capable d’identifier simplement et rapidement la route qui mène au contrôleur, et dans des scénarios de debug, cela peut s’avérer très pratique ! J’aime aussi beaucoup l’approche de se forcer à réfléchir à la route qui doit être utilisée pour chaque contrôleur et chaque action. Cela évitera notamment le syndrôme du “je mets tout dans la route par défaut et on verra plus tard, mais en fait jamais, ah si là y’a un truc qui bug…”

Il existe trois attributs qui vont permettre de gérer les routes :

  • Route : enregistrement du pattern de la route en tant que tel, des valeurs par défaut et des différentes contraintes que l’on souhaite lui appliquer
  • RoutePrefix : permet de définir un préfix de route pour tout un contrôleur
  • RouteArea : permet de définir le nom d’une area à prendre en compte au niveau de la route

Pour activer le routing par attributs dans l’application, il est nécessaire d’appeler la méthode MapMvcAttributeRoutes sur la RouteCollection, lors de l’initialisation :

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

        routes.MapMvcAttributeRoutes();
    }
}

A partir de là, votre application n’a plus aucune route de définie, même pas une route par défaut. Il va alors être nécessaire de décorer vos contrôleurs et actions avec les différents attributs de routing, par exemple :

[RoutePrefix("accueil")]
[Route("{action=index}")]
public class HomeController : Controller

En faisant cela, vous indiquez que le contrôleur peut être accédé via le préfixe “accueil” et que votre action par défaut est “index”. Du coup, l’action Index du contrôleur Home pourra être attaquée depuis les URLs :

  • /accueil
  • /accueil/index

Bien entendu, il est toujours possible de variabiliser une route pour passer des valeurs à nos actions, par exemple :

[Route("details/{productId}")]
public ActionResult ProductDetails(int productId)
{
    return View(productId);
}

Au passage, je trouve cela beaucoup plus élégant et simple à gérer que d’aller soit déclarer une nouvelle route adaptée dans le RouteConfig.cs soit de se trainer un peu partout une variable “id” qui est un coup un id de produit sous la forme d’un entier, ou alors un nom d’utilisateur en chaîne de caractères juste “pour matcher la route par défaut”.

Il est aussi possible de rajouter des contraintes sur les routes via les attributs :

[Route("details/{productId:int}")]
public ActionResult ProductDetails(int productId)
{
    return View(productId);
}

Dans le cas ci-dessus, on force la variable productId à être de type int.

Cela se complique un chouia quand vous allez vouloir ajouter des RouteConstraint personnalisées. Si l’on considère la la contrainte suivante :

public class PositiveNumberConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        object theValue;
        if (values.TryGetValue(parameterName, out theValue))
        {
            int number;
            if (int.TryParse(theValue.ToString(), out number))
            {
                return number > 0;
            }
        }

        return false;
    }
}

On pourra ajouter cette contrainte à une route, mais il va d’abord falloir la déclarer au niveau d’un “Constraint Resolver”, qui sera ensuite passé à la méthode de mapping des attributs de routing dans le RouteConfig.cs :

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

        var resolver = new DefaultInlineConstraintResolver();
        resolver.ConstraintMap.Add("positive", typeof(PositiveNumberConstraint));

        routes.MapMvcAttributeRoutes(resolver);
    }
}

Dans le cas présent, on map une contrainte “positive”. Pour l’utiliser au niveau de l’attribut, il suffira de donner le nom que l’on vient de mapper, de la manière suivante :

[Route("details/{productId:int:positive}")]
public ActionResult ProductDetails(int productId)
{
    return View(productId);
}

Je sais pas vous, mais je trouve ça plutôt cool !

A bientôt

Julien

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus