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
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:
- Compare o modelo de caminho para ver se essa convenção se aplica à solicitação atual. Se não se aplicar, retorne nulo.
- Se a convenção se aplicar, use as propriedades das instâncias ODataPathSegment para derivar nomes de controlador e ação.
- 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:
- 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.
- A convenção se aplica somente a solicitações GET e somente quando o modelo de caminho é "~/entityset/key/navigation/key".
- 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.
- 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 .