Conventions de routage dans API Web ASP.NET 2 Odata

Cet article décrit les conventions de routage utilisées par l’API Web 2 dans ASP.NET 4.x pour les points de terminaison OData.

Lorsque l’API web obtient une requête OData, elle mappe la demande à un nom de contrôleur et à un nom d’action. Le mappage est basé sur la méthode HTTP et l’URI. Par exemple, GET /odata/Products(1) mappe à ProductsController.GetProduct.

Dans la première partie de cet article, je décrit les conventions de routage OData intégrées. Ces conventions sont conçues spécifiquement pour les points de terminaison OData et remplacent le système de routage d’API web par défaut. (Le remplacement se produit lorsque vous appelez MapODataRoute.)

Dans la partie 2, je montre comment ajouter des conventions de routage personnalisées. Actuellement, les conventions intégrées ne couvrent pas toute la plage des URI OData, mais vous pouvez les étendre pour gérer des cas supplémentaires.

Conventions de routage intégrées

Avant de décrire les conventions de routage OData dans l’API web, il est utile de comprendre les URI OData. Un URI OData se compose des éléments suivants :

  • Racine du service
  • Chemin d’accès à la ressource
  • Options de requête

Capture d’écran montrant à quoi ressemblent les conventions de routage des données O, affichant la racine du service, le chemin d’accès aux ressources et les options de requête de gauche à droite.

Pour le routage, la partie importante est le chemin de la ressource. Le chemin de la ressource est divisé en segments. Par exemple, /Products(1)/Supplier a trois segments :

  • Products fait référence à un jeu d’entités nommé « Products ».
  • 1 est une clé d’entité, en sélectionnant une seule entité dans l’ensemble.
  • Supplier est une propriété de navigation qui sélectionne une entité associée.

Ce chemin choisit donc le fournisseur du produit 1.

Notes

Les segments de chemin OData ne correspondent pas toujours aux segments d’URI. Par exemple, « 1 » est considéré comme un segment de chemin d’accès.

Noms des contrôleurs. Le nom du contrôleur est toujours dérivé de l’entité définie à la racine du chemin d’accès de la ressource. Par exemple, si le chemin de la ressource est /Products(1)/Supplier, l’API web recherche un contrôleur nommé ProductsController.

Noms des actions. Les noms d’action sont dérivés des segments de chemin d’accès et du modèle de données d’entité (EDM), comme indiqué dans les tableaux suivants. Dans certains cas, vous avez deux choix pour le nom de l’action. Par exemple, « Get » ou « GetProducts ».

Interrogation d’entités

Requête Exemple d’URI Nom de l'action Exemple d’action
GET /entityset /Produits GetEntitySet ou Get GetProducts
GET /entityset(key) /Products(1) GetEntityType ou Get GetProduct
GET /entityset(key)/cast /Products(1)/Models.Book GetEntityType ou Get GetBook

Pour plus d’informations, consultez Créer un point de terminaison OData Read-Only.

Création, mise à jour et suppression d’entités

Requête Exemple d’URI Nom de l'action Exemple d’action
POST /entityset /Produits PostEntityType ou Post PostProduct
PUT /entityset(key) /Products(1) PutEntityType ou Put PutProduct
PUT /entityset(key)/cast /Products(1)/Models.Book PutEntityType ou Put PutBook
PATCH /entityset(key) /Products(1) PatchEntityType ou Patch PatchProduct
PATCH /entityset(key)/cast /Products(1)/Models.Book PatchEntityType ou Patch PatchBook
DELETE /entityset(key) /Products(1) DeleteEntityType ou Delete DeleteProduct
DELETE /entityset(key)/cast /Products(1)/Models.Book DeleteEntityType ou Delete DeleteBook

Interrogation d’une propriété de navigation

Requête Exemple d’URI Nom de l'action Exemple d’action
GET /entityset(key)/navigation /Products(1)/Supplier GetNavigationFromEntityType ou GetNavigation GetSupplierFromProduct
GET /entityset(key)/cast/navigation /Products(1)/Models.Book/Author GetNavigationFromEntityType ou GetNavigation GetAuthorFromBook

Pour plus d’informations, consultez Utilisation des relations d’entité.

Création et suppression de liens

Requête Exemple d’URI Nom de l'action
POST /entityset(key)/$links/navigation /Products(1)/$links/Supplier CreateLink
PUT /entityset(key)/$links/navigation /Products(1)/$links/Supplier CreateLink
DELETE /entityset(key)/$links/navigation /Products(1)/$links/Supplier DeleteLink
DELETE /entityset(key)/$links/navigation(relatedKey) /Products/(1)/$links/Suppliers(1) DeleteLink

Pour plus d’informations, consultez Utilisation des relations d’entité.

Propriétés

Nécessite l’API web 2

Requête Exemple d’URI Nom de l'action Exemple d’action
GET /entityset(key)/property /Products(1)/Name GetPropertyFromEntityType ou GetProperty GetNameFromProduct
GET /entityset(key)/cast/property /Products(1)/Models.Book/Author GetPropertyFromEntityType ou GetProperty GetTitleFromBook

Actions

Requête Exemple d’URI Nom de l'action Exemple d’action
POST /entityset(key)/action /Products(1)/Rate ActionNameOnEntityType ou ActionName RateOnProduct
POST /entityset(key)/cast/action /Products(1)/Models.Book/CheckOut ActionNameOnEntityType ou ActionName CheckOutOnBook

Pour plus d’informations, consultez Actions OData.

Signatures de méthode

Voici quelques règles pour les signatures de méthode :

  • Si le chemin d’accès contient une clé, l’action doit avoir un paramètre nommé key.
  • Si le chemin d’accès contient une clé dans une propriété de navigation, l’action doit avoir un paramètre nommé relatedKey.
  • Décorez les paramètres key et relatedKey avec le paramètre [FromODataUri].
  • Les requêtes POST et PUT prennent un paramètre du type d’entité.
  • Les requêtes PATCH prennent un paramètre de type Delta<T>, où T est le type d’entité.

Pour référence, voici un exemple qui montre les signatures de méthode pour chaque convention de routage OData intégrée.

public class ProductsController : ODataController
{
    // GET /odata/Products
    public IQueryable<Product> Get()

    // GET /odata/Products(1)
    public Product Get([FromODataUri] int key)

    // GET /odata/Products(1)/ODataRouting.Models.Book
    public Book GetBook([FromODataUri] int key)

    // POST /odata/Products 
    public HttpResponseMessage Post(Product item)

    // PUT /odata/Products(1)
    public HttpResponseMessage Put([FromODataUri] int key, Product item)

    // PATCH /odata/Products(1)
    public HttpResponseMessage Patch([FromODataUri] int key, Delta<Product> item)

    // DELETE /odata/Products(1)
    public HttpResponseMessage Delete([FromODataUri] int key)

    // PUT /odata/Products(1)/ODataRouting.Models.Book
    public HttpResponseMessage PutBook([FromODataUri] int key, Book item)

    // PATCH /odata/Products(1)/ODataRouting.Models.Book
    public HttpResponseMessage PatchBook([FromODataUri] int key, Delta<Book> item)

    // DELETE /odata/Products(1)/ODataRouting.Models.Book
    public HttpResponseMessage DeleteBook([FromODataUri] int key)

    //  GET /odata/Products(1)/Supplier
    public Supplier GetSupplierFromProduct([FromODataUri] int key)

    // GET /odata/Products(1)/ODataRouting.Models.Book/Author
    public Author GetAuthorFromBook([FromODataUri] int key)

    // POST /odata/Products(1)/$links/Supplier
    public HttpResponseMessage CreateLink([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)

    // DELETE /odata/Products(1)/$links/Supplier
    public HttpResponseMessage DeleteLink([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)

    // DELETE /odata/Products(1)/$links/Parts(1)
    public HttpResponseMessage DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty)

    // GET odata/Products(1)/Name
    // GET odata/Products(1)/Name/$value
    public HttpResponseMessage GetNameFromProduct([FromODataUri] int key)

    // GET /odata/Products(1)/ODataRouting.Models.Book/Title
    // GET /odata/Products(1)/ODataRouting.Models.Book/Title/$value
    public HttpResponseMessage GetTitleFromBook([FromODataUri] int key)
}

Conventions de routage personnalisées

Actuellement, les conventions intégrées ne couvrent pas tous les URI OData possibles. Vous pouvez ajouter de nouvelles conventions en implémentant l’interface IODataRoutingConvention . Cette interface a deux méthodes :

string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, 
    ILookup<string, HttpActionDescriptor> actionMap);
  • SelectController retourne le nom du contrôleur.
  • SelectAction retourne le nom de l’action.

Pour les deux méthodes, si la convention ne s’applique pas à cette demande, la méthode doit retourner null.

Le paramètre ODataPath représente le chemin de la ressource OData analysé. Il contient une liste d’instances ODataPathSegment , une pour chaque segment du chemin de la ressource. ODataPathSegment est une classe abstraite ; chaque type de segment est représenté par une classe qui dérive de ODataPathSegment.

La propriété ODataPath.TemplatePath est une chaîne qui représente la concaténation de tous les segments de chemin d’accès. Par exemple, si l’URI est /Products(1)/Supplier, le modèle de chemin est « ~/entityset/key/navigation ». Notez que les segments ne correspondent pas directement aux segments d’URI. Par exemple, la clé d’entité (1) est représentée sous la forme de son propre ODataPathSegment.

En règle générale, une implémentation d’IODataRoutingConvention effectue les opérations suivantes :

  1. Comparez le modèle de chemin d’accès pour voir si cette convention s’applique à la requête actuelle. Si elle ne s’applique pas, retournez la valeur Null.
  2. Si la convention s’applique, utilisez les propriétés des instances ODataPathSegment pour dériver des noms de contrôleur et d’action.
  3. Pour les actions, ajoutez des valeurs au dictionnaire de routes qui doivent être liées aux paramètres d’action (généralement des clés d’entité).

Examinons un exemple spécifique. Les conventions de routage intégrées ne prennent pas en charge l’indexation dans une collection de navigation. En d’autres termes, il n’existe aucune convention pour les URI comme suit :

/odata/Products(1)/Suppliers(1)

Voici une convention de routage personnalisée pour gérer ce type de requête.

using Microsoft.Data.Edm;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData.Routing;
using System.Web.Http.OData.Routing.Conventions;

namespace ODataRouting
{
    public class NavigationIndexRoutingConvention : EntitySetRoutingConvention
    {
        public override string SelectAction(ODataPath odataPath, HttpControllerContext context, 
            ILookup<string, HttpActionDescriptor> actionMap)
        {
            if (context.Request.Method == HttpMethod.Get && 
                odataPath.PathTemplate == "~/entityset/key/navigation/key")
            {
                NavigationPathSegment navigationSegment = odataPath.Segments[2] as NavigationPathSegment;
                IEdmNavigationProperty navigationProperty = navigationSegment.NavigationProperty.Partner;
                IEdmEntityType declaringType = navigationProperty.DeclaringType as IEdmEntityType;

                string actionName = "Get" + declaringType.Name;
                if (actionMap.Contains(actionName))
                {
                    // Add keys to route data, so they will bind to action parameters.
                    KeyValuePathSegment keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
                    context.RouteData.Values[ODataRouteConstants.Key] = keyValueSegment.Value;

                    KeyValuePathSegment relatedKeySegment = odataPath.Segments[3] as KeyValuePathSegment;
                    context.RouteData.Values[ODataRouteConstants.RelatedKey] = relatedKeySegment.Value;

                    return actionName;
                }
            }
            // Not a match.
            return null;
        }
    }
}

Remarques :

  1. Je dérive d’EntitySetRoutingConvention, car la méthode SelectController de cette classe est appropriée pour cette nouvelle convention de routage. Cela signifie que je n’ai pas besoin de ré-implémenter SelectController.
  2. La convention s’applique uniquement aux requêtes GET, et uniquement lorsque le modèle de chemin est « ~/entityset/key/navigation/key ».
  3. Le nom de l’action est « Get{EntityType} », où {EntityType} est le type de la collection de navigation. Par exemple, « GetSupplier ». Vous pouvez utiliser n’importe quelle convention d’affectation de noms de votre choix. Assurez-vous simplement que les actions de votre contrôleur correspondent.
  4. L’action prend deux paramètres nommés key et relatedKey. (Pour obtenir la liste de certains noms de paramètres prédéfinis, consultez ODataRouteConstants.)

L’étape suivante consiste à ajouter la nouvelle convention à la liste des conventions de routage. Cela se produit pendant la configuration, comme indiqué dans le code suivant :

using ODataRouting.Models;
using System.Web.Http;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Routing;
using System.Web.Http.OData.Routing.Conventions;

namespace ODataRouting
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
            // Create EDM (not shown).

            // Create the default collection of built-in conventions.
            var conventions = ODataRoutingConventions.CreateDefault();
            // Insert the custom convention at the start of the collection.
            conventions.Insert(0, new NavigationIndexRoutingConvention());

            config.Routes.MapODataRoute(routeName: "ODataRoute",
                routePrefix: "odata",
                model: modelBuilder.GetEdmModel(),
                pathHandler: new DefaultODataPathHandler(),
                routingConventions: conventions);

        }
    }
}

Voici d’autres exemples de conventions de routage qui sont utiles à étudier :

Bien sûr, l’API web elle-même étant open source, vous pouvez voir le code source pour les conventions de routage intégrées. Elles sont définies dans l’espace de noms System.Web.Http.OData.Routing.Conventions .