Compartir vía


Convenciones de enrutamiento en ASP.NET Web API 2 Odata

En este artículo se describen las convenciones de enrutamiento que web API 2 usa en ASP.NET 4.x para los puntos de conexión de OData.

Cuando la API web obtiene una solicitud de OData, asigna la solicitud a un nombre de controlador y un nombre de acción. La asignación se basa en el método HTTP y el URI. Por ejemplo, GET /odata/Products(1) se asigna a ProductsController.GetProduct.

En la parte 1 de este artículo, describo las convenciones de enrutamiento de OData integradas. Estas convenciones están diseñadas específicamente para los puntos de conexión de OData y reemplazan el sistema de enrutamiento de API web predeterminado. (El reemplazo se produce cuando se llama a MapODataRoute.).

En la parte 2, se muestra cómo agregar convenciones de enrutamiento personalizadas. Actualmente, las convenciones integradas no cubren todo el rango de URI de OData, pero puede ampliarlas para controlar casos adicionales.

Convenciones de enrutamiento integradas

Antes de describir las convenciones de enrutamiento de OData en la API web, resulta útil comprender los URI de OData. Un URI de OData consta de:

  • Raíz del servicio
  • Ruta de acceso del recurso
  • Opciones de consulta

Screenshot to show what the O Data routing conventions looks like, displaying service root, resource path, and query options from left to right.

Para el enrutamiento, la parte importante es la ruta de acceso del recurso. La ruta de acceso del recurso se divide en segmentos. Por ejemplo, /Products(1)/Supplier tiene tres segmentos:

  • Products hace referencia a un conjunto de entidades denominado "Products".
  • 1 es una clave de entidad, seleccionando una sola entidad del conjunto.
  • Supplier es una propiedad de navegación que selecciona una entidad relacionada.

Por lo tanto, esta ruta elige el proveedor del producto 1.

Nota:

Los segmentos de ruta de acceso de OData no siempre se corresponden con los segmentos de URI. Por ejemplo, "1" se considera un segmento de ruta de acceso.

Nombres de controlador. El nombre del controlador siempre se deriva de la entidad establecida en la raíz de la ruta de acceso del recurso. Por ejemplo, si la ruta de acceso del recurso es /Products(1)/Supplier, la API web busca un controlador denominado ProductsController.

Nombres de acción. Los nombres de acción se derivan de los segmentos de ruta de acceso más el modelo de datos de entidad (EDM), como se muestra en las tablas siguientes. En algunos casos, tiene dos opciones para el nombre de la acción. Por ejemplo, "Get" o "GetProducts".

Consulta de entidades

Solicitar URI de ejemplo Nombre de acción Acción de ejemplo
GET /entityset /Productos GetEntitySet o Get GetProducts
GET /entityset(key) /Products(1) GetEntityType o Get GetProduct
GET /entityset(key)/cast /Products(1)/Models.Book GetEntityType o Get GetBook

Para obtener más información, vea Crear un punto de conexión de OData de solo lectura.

Creación, actualización y eliminación de entidades

Solicitar URI de ejemplo Nombre de acción Acción de ejemplo
PUBLICAR /entityset /Productos PostEntityType o Post PostProduct
PUT /entityset(key) /Products(1) PutEntityType o Put PutProduct
PUT /entityset(key)/conversión /Products(1)/Models.Book PutEntityType o Put PutBook
PATCH /entityset(key) /Products(1) PatchEntityType o Revisión PatchProduct
PATCH /entityset(key)/conversión /Products(1)/Models.Book PatchEntityType o Revisión PatchBook
DELETE /entityset(key) /Products(1) DeleteEntityType o Eliminar DeleteProduct
DELETE /entityset(key)/cast /Products(1)/Models.Book DeleteEntityType o Eliminar DeleteBook

Consulta de una propiedad de navegación

Solicitar URI de ejemplo Nombre de acción Acción de ejemplo
GET /entityset(key)/navegación /Products(1)/Supplier GetNavigationFromEntityType o GetNavigation GetSupplierFromProduct
GET /entityset(key)/cast/navigation /Products(1)/Models.Book/Author GetNavigationFromEntityType o GetNavigation GetAuthorFromBook

Para obtener más información, consulte Trabajar con relaciones de entidad.

Crear y eliminar vínculos

Solicitar URI de ejemplo Nombre de acción
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/Providers(1) DeleteLink

Para obtener más información, consulte Trabajar con relaciones de entidad.

Propiedades

Requiere Web API 2

Solicitar URI de ejemplo Nombre de acción Acción de ejemplo
GET /entityset(key)/property /Products(1)/Name GetPropertyFromEntityType o GetProperty GetNameFromProduct
GET /entityset(key)/cast/property /Products(1)/Models.Book/Author GetPropertyFromEntityType o GetProperty GetTitleFromBook

Acciones

Solicitar URI de ejemplo Nombre de acción Acción de ejemplo
POST /entityset(key)/action /Products(1)/Rate ActionNameOnEntityType o ActionName RateOnProduct
POST /entityset(key)/cast/action /Products(1)/Models.Book/CheckOut ActionNameOnEntityType o ActionName CheckOutOnBook

Para obtener más información, consulte acciones de OData.

Firmas de método

Estas son algunas reglas para las firmas de método:

  • Si la ruta de acceso contiene una clave, la acción debe tener un parámetro denominado clave.
  • Si la ruta de acceso contiene una clave en una propiedad de navegación, la acción debe tener un parámetro denominado relatedKey.
  • Decorar los parámetros clave y relatedKey con el parámetro [FromODataUri].
  • Las solicitudes POST y PUT toman un parámetro del tipo de entidad.
  • Las solicitudes PATCH toman un parámetro de tipo Delta<T>, donde T es el tipo de entidad.

Como referencia, este es un ejemplo que muestra las firmas de método para cada convención de enrutamiento de OData integrada.

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)
}

Convenciones de enrutamiento personalizadas

Actualmente, las convenciones integradas no cubren todos los URI de OData posibles. Puede agregar nuevas convenciones implementando la interfaz IODataRoutingConvention. Esta interfaz tiene dos métodos:

string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, 
    ILookup<string, HttpActionDescriptor> actionMap);
  • SelectController devuelve el nombre del controlador.
  • SelectAction devuelve el nombre de la acción.

Para ambos métodos, si la convención no se aplica a esa solicitud, el método debe devolver null.

El parámetro ODataPath representa la ruta de acceso del recurso de OData analizada. Contiene una lista de instancias de ODataPathSegment, una para cada segmento de la ruta de acceso del recurso. ODataPathSegment es una clase abstracta; cada tipo de segmento se representa mediante una clase que deriva de ODataPathSegment.

La propiedad ODataPath.TemplatePath es una cadena que representa la concatenación de todos los segmentos de ruta de acceso. Por ejemplo, si el URI es /Products(1)/Supplier, la plantilla de ruta de acceso es "~/entityset/key/navigation". Observe que los segmentos no se corresponden directamente con los segmentos de URI. Por ejemplo, la clave de entidad (1) se representa como su propia ODataPathSegment.

Normalmente, una implementación de IODataRoutingConvention hace lo siguiente:

  1. Compare la plantilla de ruta de acceso para ver si esta convención se aplica a la solicitud actual. Si no se aplica, devuelva null.
  2. Si se aplica la convención, use las propiedades del ODataPathSegment instancias para derivar nombres de controlador y acción.
  3. En el caso de las acciones, agregue los valores al diccionario de rutas que se deben enlazar a los parámetros de acción (normalmente claves de entidad).

Echemos un vistazo a un ejemplo específico. Las convenciones de enrutamiento integradas no admiten la indexación en una colección de navegación. En otras palabras, no hay ninguna convención para los URI como los siguientes:

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

Esta es una convención de enrutamiento personalizada para controlar este tipo de consulta.

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;
        }
    }
}

Notas:

  1. Deriva de EntitySetRoutingConvention, porque el método SelectController de esa clase es adecuado para esta nueva convención de enrutamiento. Esto significa que no necesito volver a implementar SelectController.
  2. La convención solo se aplica a las solicitudes GET y solo cuando la plantilla de ruta de acceso es "~/entityset/key/navigation/key".
  3. El nombre de la acción es "Get{EntityType}", donde {EntityType} es el tipo de la colección de navegación. Por ejemplo, "GetSupplier". Puede usar cualquier convención de nomenclatura que le guste, simplemente asegúrese de que las acciones del controlador coincidan.
  4. La acción realiza dos parámetros denominados clave y relatedKey. (Para obtener una lista de algunos nombres de parámetro predefinidos, consulte ODataRouteConstants.)

El siguiente paso consiste en agregar la nueva convención a la lista de convenciones de enrutamiento. Esto sucede durante la configuración, como se muestra en el código siguiente:

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);

        }
    }
}

Estas son otras convenciones de enrutamiento de ejemplo que son útiles para estudiar:

Y, por supuesto, la propia Web API es de código abierto, por lo que puede ver el código fuente para las convenciones de enrutamiento integradas. Se definen en el espacio de nombres System.Web.Http.Http.OData.Routing.Conventions .