Partilhar via


Convenções de roteamento no Odata ASP.NET Web API 2

Este artigo descreve as convenções de roteamento que a API Web 2 no ASP.NET 4.x usa para pontos de extremidade OData.

Quando a API Web obtém uma solicitação OData, ela mapeia a solicitação para um nome de controlador e um nome de ação. O mapeamento é baseado no método HTTP e no URI. Por exemplo, GET /odata/Products(1) mapeia para ProductsController.GetProduct.

Na parte 1 deste artigo, descrevo as convenções internas de roteamento OData. Essas convenções são projetadas especificamente para pontos de extremidade OData e substituem o sistema de roteamento de API Web padrão. (A substituição acontece quando você chama MapODataRoute.)

Na parte 2, mostro como adicionar convenções de roteamento personalizadas. Atualmente, as convenções internas não abrangem todo o intervalo de URIs OData, mas você pode estendê-las para lidar com casos adicionais.

Convenções internas de roteamento

Antes de descrever as convenções de roteamento OData na API Web, é útil entender os URIs do OData. Um URI OData consiste em:

  • A raiz do serviço
  • O caminho do recurso
  • Opções de consulta

Captura de tela para mostrar a aparência das convenções de roteamento de dados O, exibindo a raiz do serviço, o caminho do recurso e as opções de consulta da esquerda para a direita.

Para roteamento, a parte importante é o caminho do recurso. O caminho do recurso é dividido em segmentos. Por exemplo, /Products(1)/Supplier tem três segmentos:

  • Products refere-se a um conjunto de entidades chamado "Produtos".
  • 1 é uma chave de entidade, selecionando uma única entidade no conjunto.
  • Supplier é uma propriedade de navegação que seleciona uma entidade relacionada.

Portanto, esse caminho escolhe o fornecedor do produto 1.

Observação

Os segmentos de caminho OData nem sempre correspondem a segmentos de URI. Por exemplo, "1" é considerado um segmento de caminho.

Nomes do controlador. O nome do controlador é sempre derivado da entidade definida na raiz do caminho do recurso. Por exemplo, se o caminho do recurso for /Products(1)/Supplier, a API Web procurará um controlador chamado ProductsController.

Nomes de ação. Os nomes de ação são derivados dos segmentos de caminho mais o EDM (modelo de dados de entidade), conforme listado nas tabelas a seguir. Em alguns casos, você tem duas opções para o nome da ação. Por exemplo, "Get" ou "GetProducts".

Consultando entidades

Solicitação Exemplo de URI Nome da ação Ação de exemplo
GET /entityset /Produtos 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

Para obter mais informações, consulte Criar um ponto de extremidade OData Read-Only.

Criando, atualizando e excluindo entidades

Solicitação Exemplo de URI Nome da ação Ação de exemplo
POST /entityset /Produtos 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

Consultando uma propriedade de navegação

Solicitação Exemplo de URI Nome da ação Ação de exemplo
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

Para obter mais informações, consulte Trabalhando com relações de entidade.

Criando e excluindo links

Solicitação Exemplo de URI Nome da ação
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

Para obter mais informações, consulte Trabalhando com relações de entidade.

Propriedades

Requer a API Web 2

Solicitação Exemplo de URI Nome da ação Ação de exemplo
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

Ações

Solicitação Exemplo de URI Nome da ação Ação de exemplo
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

Para obter mais informações, consulte OData Actions.

Assinaturas de método

Aqui estão algumas regras para as assinaturas de método:

  • Se o caminho contiver uma chave, a ação deverá ter um parâmetro chamado key.
  • Se o caminho contiver uma chave em uma propriedade de navegação, a ação deverá ter um parâmetro chamado relatedKey.
  • Decore os parâmetros key e relatedKey com o parâmetro [FromODataUri] .
  • As solicitações POST e PUT tomam um parâmetro do tipo de entidade.
  • As solicitações PATCH levam um parâmetro do tipo Delta<T>, em que T é o tipo de entidade.

Para referência, aqui está um exemplo que mostra assinaturas de método para cada convenção interna de roteamento OData.

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

Convenções de roteamento personalizado

Atualmente, as convenções internas não abrangem todos os URIs OData possíveis. Você pode adicionar novas convenções implementando a interface IODataRoutingConvention . Essa interface tem dois métodos:

string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, 
    ILookup<string, HttpActionDescriptor> actionMap);
  • SelectController retorna o nome do controlador.
  • SelectAction retorna o nome da ação.

Para ambos os métodos, se a convenção não se aplicar a essa solicitação, o método deverá retornar nulo.

O parâmetro ODataPath representa o caminho de recurso OData analisado. Ele contém uma lista de instâncias ODataPathSegment , uma para cada segmento do caminho do recurso. ODataPathSegment é uma classe abstrata; cada tipo de segmento é representado por uma classe derivada de ODataPathSegment.

A propriedade ODataPath.TemplatePath é uma cadeia de caracteres que representa a concatenação de todos os segmentos de caminho. Por exemplo, se o URI for /Products(1)/Supplier, o modelo de caminho será "~/entityset/key/navigation". Observe que os segmentos não correspondem diretamente aos segmentos de URI. Por exemplo, a chave de entidade (1) é representada como seu próprio ODataPathSegment.

Normalmente, uma implementação de IODataRoutingConvention faz o seguinte:

  1. Compare o modelo de caminho para ver se essa convenção se aplica à solicitação atual. Se não se aplicar, retorne nulo.
  2. Se a convenção se aplicar, use as propriedades das instâncias ODataPathSegment para derivar nomes de controlador e ação.
  3. Para ações, adicione todos os valores ao dicionário de rotas que devem ser associados aos parâmetros de ação (normalmente chaves de entidade).

Vamos examinar um exemplo específico. As convenções internas de roteamento não dão suporte à indexação em uma coleção de navegação. Em outras palavras, não há nenhuma convenção para URIs como o seguinte:

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

Aqui está uma convenção de roteamento personalizada para lidar com esse 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;
        }
    }
}

Observações:

  1. Sou derivado de EntitySetRoutingConvention, pois o método SelectController nessa classe é apropriado para essa nova convenção de roteamento. Isso significa que não preciso implementar novamente SelectController.
  2. A convenção se aplica somente a solicitações GET e somente quando o modelo de caminho é "~/entityset/key/navigation/key".
  3. O nome da ação é "Get{EntityType}", em que {EntityType} é o tipo da coleção de navegação. Por exemplo, "GetSupplier". Você pode usar qualquer convenção de nomenclatura desejada , apenas verifique se as ações do controlador correspondem.
  4. A ação usa dois parâmetros chamados key e relatedKey. (Para obter uma lista de alguns nomes de parâmetro predefinidos, consulte ODataRouteConstants.)

A próxima etapa é adicionar a nova convenção à lista de convenções de roteamento. Isso ocorre durante a configuração, conforme mostrado no seguinte código:

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

        }
    }
}

Aqui estão algumas outras convenções de roteamento de exemplo que são úteis para estudar:

E, claro, a própria API Web é de software livre, para que você possa ver o código-fonte para as convenções internas de roteamento. Eles são definidos no namespace System.Web.Http.OData.Routing.Conventions .