ASP.NET Web API 2 Odata 中的路由约定
本文介绍 ASP.NET 4.x 中的 Web API 2 用于 OData 终结点的路由约定。
Web API 获取 OData 请求时,它会将请求映射到控制器名称和操作名称。 映射基于 HTTP 方法和 URI。 例如, GET /odata/Products(1)
映射到 ProductsController.GetProduct
。
本文的第 1 部分介绍了内置的 OData 路由约定。 这些约定专为 OData 终结点设计,它们取代了默认的 Web API 路由系统。 (调用 MapODataRoute.)
第 2 部分介绍如何添加自定义路由约定。 目前,内置约定未涵盖整个 OData URI 范围,但你可以扩展它们以处理其他情况。
内置路由约定
在介绍 Web API 中的 OData 路由约定之前,了解 OData URI 会很有帮助。 OData URI 包括:
- 服务根
- 资源路径
- 查询选项
对于路由,重要的是资源路径。 资源路径分为多个段。 例如, /Products(1)/Supplier
有三个段:
Products
引用名为“Products”的实体集。1
是实体键,从集中选择单个实体。Supplier
是选择相关实体的导航属性。
因此,此路径选择产品 1 的供应商。
注意
OData 路径段并不总是与 URI 段相对应。 例如,“1”被视为路径段。
控制器名称。 控制器名称始终派生自资源路径根目录中的实体集。 例如,如果资源路径为 /Products(1)/Supplier
,则 Web API 会查找名为 的 ProductsController
控制器。
操作名称。 操作名称派生自路径段和实体数据模型 (EDM) ,如下表所示。 在某些情况下,操作名称有两种选择。 例如,“Get”或“GetProducts”。
查询实体
请求 | 示例 URI | 操作名称 | 示例操作 |
---|---|---|---|
GET /entityset | /产品 | GetEntitySet 或 Get | GetProducts |
GET /entityset (key) | /Products (1) | GetEntityType 或 Get | GetProduct |
GET /entityset (键) /cast | /Products (1) /Models.Book | GetEntityType 或 Get | GetBook |
有关详细信息,请参阅 创建Read-Only OData 终结点。
创建、更新和删除实体
请求 | 示例 URI | 操作名称 | 示例操作 |
---|---|---|---|
POST /entityset | /产品 | PostEntityType 或 Post | PostProduct |
PUT /entityset (键) | /Products (1) | PutEntityType 或 Put | PutProduct |
PUT /entityset (键) /cast | /Products (1) /Models.Book | PutEntityType 或 Put | PutBook |
PATCH /entityset (key) | /Products (1) | PatchEntityType 或 Patch | PatchProduct |
PATCH /entityset (键) /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 (键) /navigation | /Products (1) /Supplier | GetNavigationFromEntityType 或 GetNavigation | GetSupplierFromProduct |
GET /entityset (键) /cast/navigation | /Products (1) /Models.Book/Author | GetNavigationFromEntityType 或 GetNavigation | GetAuthorFromBook |
有关详细信息,请参阅 使用实体关系。
创建和删除链接
请求 | 示例 URI | 操作名称 |
---|---|---|
POST /entityset (键) /$links/navigation | /Products (1) /$links/Supplier | CreateLink |
PUT /entityset (键) /$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 |
有关详细信息,请参阅 使用实体关系。
属性
需要 Web API 2
请求 | 示例 URI | 操作名称 | 示例操作 |
---|---|---|---|
GET /entityset (键) /property | /Products (1) /Name | GetPropertyFromEntityType 或 GetProperty | GetNameFromProduct |
GET /entityset (键) /cast/property | /Products (1) /Models.Book/Author | GetPropertyFromEntityType 或 GetProperty | GetTitleFromBook |
操作
请求 | 示例 URI | 操作名称 | 示例操作 |
---|---|---|---|
POST /entityset (键) /action | /Products (1) /Rate | ActionNameOnEntityType 或 ActionName | RateOnProduct |
POST /entityset (键) /cast/action | /Products (1) /Models.Book/CheckOut | ActionNameOnEntityType 或 ActionName | CheckOutOnBook |
有关详细信息,请参阅 OData 操作。
方法签名
下面是方法签名的一些规则:
- 如果路径包含键,则操作应具有名为 key 的参数。
- 如果路径包含导航属性中的键,则操作应具有名为 relatedKey 的参数。
- 使用 [FromODataUri] 参数修饰键和 relatedKey 参数。
- 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)
}
自定义路由约定
目前,内置约定并不涵盖所有可能的 OData URI。 可以通过实现 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);
}
}
}
下面是一些其他可用于研究的示例路由约定:
当然,Web API 本身是开源的,因此可以查看内置路由约定的 源代码 。 这些在 System.Web.Http.OData.Routing.Conventions 命名空间中定义。