Соглашения о маршрутизации в Odata веб-API ASP.NET 2
В этой статье описываются соглашения о маршрутизации, которые веб-API 2 в ASP.NET 4.x использует для конечных точек OData.
Когда веб-API получает запрос OData, он сопоставляет запрос с именем контроллера и именем действия. Сопоставление основано на методе HTTP и универсальном коде ресурса (URI). Например, GET /odata/Products(1)
сопоставляется с ProductsController.GetProduct
.
В части 1 этой статьи я описываю встроенные соглашения о маршрутизации OData. Эти соглашения разработаны специально для конечных точек OData и заменяют систему маршрутизации веб-API по умолчанию. (Замена происходит при вызове MapODataRoute.)
В части 2 я показываю, как добавить пользовательские соглашения о маршрутизации. В настоящее время встроенные соглашения не охватывают весь диапазон URI OData, но их можно расширить для обработки дополнительных случаев.
Встроенные соглашения о маршрутизации
Прежде чем описывать соглашения о маршрутизации OData в веб-API, полезно понять URI OData. URI OData состоит из следующих элементов:
- Корневой каталог службы
- Путь к ресурсу
- Параметры запроса
Для маршрутизации важной частью является путь к ресурсам. Путь к ресурсу делится на сегменты. Например, /Products(1)/Supplier
имеет три сегмента:
Products
ссылается на набор сущностей с именем "Products".1
— это ключ сущности, который выбирает одну сущность из набора.Supplier
— это свойство навигации, которое выбирает связанную сущность.
Таким образом, этот путь выбирает поставщика продукта 1.
Примечание
Сегменты пути OData не всегда соответствуют сегментам URI. Например, "1" считается сегментом пути.
Имена контроллеров. Имя контроллера всегда является производным от набора сущностей в корне пути к ресурсу. Например, если путь к ресурсу — /Products(1)/Supplier
, веб-API ищет контроллер с именем ProductsController
.
Имена действий. Имена действий являются производными от сегментов пути и модели данных сущности (EDM), как указано в следующих таблицах. В некоторых случаях имя действия можно выбрать двумя вариантами. Например, Get или GetProducts.
Запрос сущностей
Запрос | Пример URI | Название действия | Пример действия |
---|---|---|---|
GET /entityset | /Продукты | GetEntitySet или Get | GetProducts |
GET /entityset(key) | /Products(1) | GetEntityType или Get | GetProduct |
GET /entityset(key)/cast | /Products(1)/Models.Book | GetEntityType или Get | GetBook |
Дополнительные сведения см. в статье Создание конечной точки OData Read-Only.
Создание, обновление и удаление сущностей
Запрос | Пример URI | Название действия | Пример действия |
---|---|---|---|
POST /entityset | /Продукты | PostEntityType или Post | PostProduct |
PUT /entityset(key) | /Products(1) | PutEntityType или Put | PutProduct |
PUT /entityset(key)/cast | /Products(1)/Models.Book | PutEntityType или Put | PutBook |
PATCH /entityset(key) | /Products(1) | PatchEntityType или Patch | PatchProduct |
PATCH /entityset(key)/cast | /Products(1)/Models.Book | PatchEntityType или Patch | PatchBook |
DELETE /entityset(key) | /Products(1) | DeleteEntityType или Delete | DeleteProduct |
DELETE /entityset(key)/cast | /Products(1)/Models.Book | DeleteEntityType или Delete | DeleteBook |
Запрос свойства навигации
Запрос | Пример URI | Название действия | Пример действия |
---|---|---|---|
GET /entityset(key)/navigation | /Products(1)/Поставщик | GetNavigationFromEntityType или GetNavigation | GetSupplierFromProduct |
GET /entityset(key)/cast/navigation | /Products(1)/Models.Book/Author | GetNavigationFromEntityType или GetNavigation | GetAuthorFromBook |
Дополнительные сведения см. в разделе Работа с отношениями сущностей.
Создание и удаление ссылок
Запрос | Пример URI | Название действия |
---|---|---|
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 |
Дополнительные сведения см. в разделе Работа с отношениями сущностей.
Свойства
Требуется веб-API 2
Запрос | Пример URI | Название действия | Пример действия |
---|---|---|---|
GET /entityset(key)/property | /Products(1)/Name | GetPropertyFromEntityType или GetProperty | GetNameFromProduct |
GET /entityset(key)/cast/property | /Products(1)/Models.Book/Author | GetPropertyFromEntityType или GetProperty | GetTitleFromBook |
Действия
Запрос | Пример URI | Название действия | Пример действия |
---|---|---|---|
POST /entityset(key)/action | /Products(1)/Rate | ActionNameOnEntityType или ActionName | RateOnProduct |
POST /entityset(key)/cast/action | /Products(1)/Models.Book/CheckOut | ActionNameOnEntityType или ActionName | CheckOutOnBook |
Дополнительные сведения см. в разделе OData Actions.
Сигнатуры методов
Ниже приведены некоторые правила для сигнатур методов.
- Если путь содержит ключ, действие должно иметь параметр с именем key.
- Если путь содержит ключ в свойстве навигации, действие должно иметь параметр relatedKey.
- Укажите параметры key и relatedKey с помощью параметра [FromODataUri] .
- Запросы POST и PUT принимают параметр типа сущности.
- Запросы PATCH принимают параметр типа Delta<T>, где T — это тип сущности.
Для справки ниже приведен пример сигнатуры методов для каждого встроенного соглашения о маршрутизации 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)
}
Настраиваемые соглашения о маршрутизации
В настоящее время встроенные соглашения не охватывают все возможные URI OData. Вы можете добавить новые соглашения, реализовав интерфейс IODataRoutingConvention . Этот интерфейс имеет два метода:
string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext,
ILookup<string, HttpActionDescriptor> actionMap);
- SelectController возвращает имя контроллера.
- SelectAction возвращает имя действия.
Для обоих методов, если соглашение не применяется к такому запросу, метод должен возвращать значение NULL.
Параметр ODataPath представляет проанализированный путь к ресурсу OData. Он содержит список экземпляров ODataPathSegment , по одному для каждого сегмента пути к ресурсу. ODataPathSegment — это абстрактный класс; Каждый тип сегмента представлен классом, производным от ODataPathSegment.
Свойство ODataPath.TemplatePath — это строка, представляющая объединение всех сегментов пути. Например, если URI имеет значение /Products(1)/Supplier
, шаблон пути — "~/entityset/key/navigation". Обратите внимание, что сегменты не соответствуют напрямую сегментам URI. Например, ключ сущности (1) представлен как собственный ODataPathSegment.
Как правило, реализация IODataRoutingConvention выполняет следующие действия:
- Сравните шаблон пути, чтобы узнать, применяется ли это соглашение к текущему запросу. Если он не применяется, возвращается значение NULL.
- Если применяется соглашение, используйте свойства экземпляров ODataPathSegment для получения имен контроллеров и действий.
- Для действий добавьте все значения в словарь маршрутов, которые должны быть привязаны к параметрам действия (обычно это ключи сущностей).
Рассмотрим конкретный пример. Встроенные соглашения о маршрутизации не поддерживают индексирование в коллекции навигации. Иными словами, для URI не существует соглашения, подобного следующему:
/odata/Products(1)/Suppliers(1)
Ниже приведено пользовательское соглашение о маршрутизации для обработки запросов этого типа.
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;
}
}
}
Примечания.
- Я являюсь производным от EntitySetRoutingConvention, так как метод SelectController в этом классе подходит для этого нового соглашения о маршрутизации. Это означает, что мне не нужно повторно реализовывать SelectController.
- Соглашение применяется только к запросам GET и только в том случае, если шаблон пути — "~/entityset/key/navigation/key".
- Имя действия — Get{EntityType}, где {EntityType} — это тип коллекции навигации. Например, GetSupplier. Вы можете использовать любое соглашение об именовании, которое вам нравится— просто убедитесь, что действия контроллера совпадают.
- Действие принимает два параметра с именами key и relatedKey. (Список некоторых стандартных имен параметров см. в разделе ODataRouteConstants.)
Следующий шаг — добавление нового соглашения в список соглашений о маршрутизации. Это происходит во время настройки, как показано в следующем коде:
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);
}
}
}
Ниже приведены некоторые другие примеры соглашений о маршрутизации, которые будут полезны для изучения:
И, конечно, сам веб-API имеет открытый код, поэтому вы можете просмотреть исходный код для встроенных соглашений о маршрутизации. Они определены в пространстве имен System.Web.Http.OData.Routing.Conventions .