Lors du précédent article, je vous ai expliqué comment il est possible très facilement de modifier la façon dont ASP.net WebAPI2 enregistre et résout les contrôleurs afin de prendre en compte le nom complet, namespace inclus, des contrôleurs.
Nous allons voir dans cet article comment enrichir les données des DataTokens et surtout comment nommer les routes automatiquement.
Pourquoi nommer les routes ?
Vous allez me dire : mais pourquoi vouloir nommer les routes dans WebAPI2 puisque nous l’utilisons essentiellement depuis une autre application voire un autre service !?!
En fait, c’est très simple : dans certaines conditions, il est nécessaire de retourner un pointeur vers une resource, c’est le cas par exemple lorsque vous faites un POST
pour créer une entité, si l’opération aboutie, conformément à la RFC-2616, section 9.5, vous devez retourner un code HTTP 201
, mais aussi l’entité nouvellement créée et un lien vers cette resource ; typiquement, l’opération GET correspondante.
Par exemple : mon opération POST /clients
aboutie. Je dois donc obtenir la réponse suivante :
HTTP/1.1 201 CREATED ... Location: /clients/5 Content-Type: application/json; charset=UTF-8 { "id" : "5", "firstname":"sébastien"...}
Il existe bien sûr plusieurs autres cas pour lesquels il est intéressant de pouvoir retourner un lien vers une resource :
- La pagination (qui fera l’objet d’un prochain article) ;
- Sous resources d’une entité (
HATEOAS
) ; - Plusieurs versions d’une même entités (v1, v2, v3…)
Comment ça marche alors ?
Et bien, comme toujours : c’est simple… ou presque, il suffit de savoir où se brancher !
Nous allons donc créer un RouteProvider, ou plutôt en surcharger un : DefaultDirectRouteProvider
, qui lui-même est l’implémentation par défaut de l’interface IDirectRouteProvider
qui permet de définir un fournisseur (provider) pour les routes qui ciblent les actions avec un attribut [Route]
.
Ce qui m’intéresse ici, c’est uniquement de surcharger 2 méthodes : GetActionRouteFactories
et GetDirectRoutes
.
La première va me permettre de créer une collection de toutes les actions disponibles pour mon application et la seconde obtient les itinéraires directs pour le descripteur de contrôleur donné et les descripteurs d’action en fonction des attributs.
protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories( HttpActionDescriptor actionDescriptor) { var directRoutes = base.GetActionRouteFactories(actionDescriptor); var routes = new List<IDirectRouteFactory>(); foreach (var r in directRoutes) { var ra = r as RouteAttribute; if (r == null) { routes.Add(r); continue; } var route = new RouteAttribute(ra.Template) { Name = string.Format("{0}.{1}.{2}", actionDescriptor.ControllerDescriptor.ControllerType.Namespace, actionDescriptor.ControllerDescriptor.ControllerName, actionDescriptor.ActionName) }; routes.Add(route); } return routes; }
A la ligne 4, je récupère les informations fournies par la classe de base, puis pour chaque route retournée, je regarde si elle possède un attribut [Route]
, si non, je passe mon tour, si oui, je crée une route à partir du template de l’attribut et je nomme cette route avec le nom complet du contrôleur (namespace + contrôleur) que je suffixe avec le nom de l’action. Par exemple, pour l’action GetById(int id)
de mon contrôleur MonAPI.Controllers.ClientsController
, j’obtiendrai : MonAPI.Controllers.Clients.GetById
.
A partir de maintenant, il est très simple d’obtenir la route de n’importe quelle opération de WebApi2 en utilisant UrlHelper
, par exemple :
var url = RequestContent.Url.Link("MonApi.Controllers.Clients.GetById", new { id = 5 });
Pendant qu’on est là !
Oui, c’est parfois intéressant d’avoir un peu plus d’informations stockées avec nos routes, c’est à ça que sert la propriété DataTokens
.
Pour cela, je vais vous montrer comment on peut ajouter facilement des informations complémentaires en surchangeant la méthode GetDirectRoutes
public override IReadOnlyList<RouteEntry> GetDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList<HttpActionDescriptor> actionDescriptors, IInlineConstraintResolver constraintResolver) { var coll = base.GetDirectRoutes(controllerDescriptor, actionDescriptors, constraintResolver); foreach (var routeEntry in coll.Where(x => !string.IsNullOrEmpty(x.Name))) { routeEntry.Route.DataTokens["routeName"] = routeEntry.Name; routeEntry.Route.DataTokens["namespace"] = controllerDescriptor.ControllerType.Namespace; routeEntry.Route.DataTokens["controller"] = controllerDescriptor.ControllerName; if (routeEntry.Route.DataTokens.ContainsKey("actions")) { routeEntry.Route.DataTokens["httpMethod"] = String.Join(",", (routeEntry.Route.DataTokens["actions"] as IEnumerable<HttpActionDescriptor>) .First() .SupportedHttpMethods .Select(m=>m.Method)); } } return coll; }
Dans cet exemple, j’ajoute le nom de la route, le namespace, le nom du contrôleur ainsi que les méthodes HTTP supportées aux DataTokens
. Il est bien sûr possible d’ajouter bien plus d’informations en fonction de vos besoins.
Déclaration du DirectRouteProvider
Bon, pour le moment, nous avons fait tout cela pour rien, car WebAPI2 ignore purement et simplement notre provider puisque nous ne l’avons pas déclaré !
Pour cela, on va modifier une ligne dans la méthode Register
de la classe WebApiConfig
// config.MapHttpAttributeRoutes(); config.MapHttpAttributeRoutes(new CustomDefaultDirectRouteProvider());
Dans cet exemple, CustomDefaultDirectRouteProvider
est le nom de notre classe dérivant de DefaultDirectRouteProvider
et contenant les surcharges des méthodes GetActionRouteFactories
et GetDirectRoutes
que nous avons vu précédemment.