原文地址:http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-routing-conventions
本文介绍的是Web API中使用的odata endpoints 的路由约定。
术语
URI:统一资源标识符(Uniform Resource Identifier,或URI)是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对网络中(一般指万维网)的资源通过特定的协议进行交互操作。URI包括URL和URN。
当Web API得到一个OData请求,它会自动映射到相应的Controller和Action名字。这个映射是基于HTTP方法和URI的。例如:GET /odata/Products(1) 映射到ProductsController.GetProduct.
本文Part 1介绍的是内置的OData路由约定。这些路由约定是专门为OData endpoints设计的,并且他们覆盖了默认的Web API路由系统(当调用MapODataRoute时覆盖将会发生)。
本文Part 2介绍的是如何添加自定义的OData路由约定。目前内置的OData路由约定并没有覆盖到所有的OData URIs,但是你可以扩展他们来处理额外的案例。
内置路由约定
首先下面有些方便理解OData Uri的准备知识:
URI 组成: 服务器根service root,资源路径resource path,查询选项Query options。
针对路由,最重要的部分是Resource Path。Resource Path被分成片段。例如上面的URI例子"Products(1)/Supplier"有三个片段:
- Products: 代表名为Products的entity set。
- 1:表示entity的主键,从entity set中短暂单个entity。
- Supplier:属于导航属性(navigation property),选择相关联entity。
所以上面的例子查询结果:product 1的Supplier。
Controller Name
Controller Name 通常都是衍生于Resource Path根部的entity set。继续上面的例子“Products(1)/Supplier”:Web API查找名为ProductsController的controller。
Action Name
Action Name通常衍生于路径片段加上实体数据模型(EDM: Entity Data Model),如下表中所示。有时候,你有两种Action Name选择,如"Get"和“GetProduct”。
-
查询entities
URI例子 | 操作名字(Action) | Action例子 | |
GET /entityset | /Products | GetEntitySet or Get | GetProducts |
GET /entityset(key) | /Products(1) | GetEntityType or Get | GetProduct |
GET /entityset(key)/cast | /Products(1)/Models.Book | GetEntityType or Get | GetBook |
更多的资料查看:http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v3/creating-an-odata-endpoint(OData V3)
-
增、删、改entities
请求类型 | URI例子 | 操作名字(Action) | Action例子 |
POST /entityset(增) | /Products | PostEntityType or Post | PostProduct |
PUT /entityset(key)(改) | /Products(1) | PutEntityType or Put | PutProduct |
PUT /entityset(key)/cast(改) | /Products(1)/Models.Book | PutEntityType or Put | PutBook |
PATCH /entityset(key)(改) | /Products(1) | PatchEntityType or Patch | PatchProduct |
PATCH /entityset(key)/cast(改) | /Products(1)/Models.Book | PatchEntityType or Patch | PatchBook |
DELETE /entityset(key)(删) | /Products(1) | DeleteEntityType or Delete | DeleteProduct |
DELETE /entityset(key)/cast(删) | /Products(1)/Models.Book | DeleteEntityType or Delete | DeleteBook |
-
查询导航属性( Navigation Property)
URI例子 | Action Name | Action例子 | |
GET /entityset(key)/navigation | /Products(1)/Supplier | GetNavigationFromEntityType or GetNavigation | GetSupplierFromProduct |
GET /entityset(key)/cast/navigation | /Products(1)/Models.Book/Author | GetNavigationFromEntityType or GetNavigation | GetAuthorFromBook |
更多信息查看:Working with Entity Relations.
-
增、删entities之间的联系(links)
请求类型 | URI例子 | Action Name |
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 |
更多信息查看:Working with Entity Relations.
-
属性properties(需要Web API 2)
URI例子 | Action Name | Action例子 | |
GET /entityset(key)/property | /Products(1)/Name | GetPropertyFromEntityType or GetProperty | GetTitleFromBook |
GET /entityset(key)/cast/property | /Products(1)/Models.Book/Author | GetPropertyFromEntityType or GetProperty | GetTitleFromBook |
-
操作Action
URI例子 | Action Name | Action例子 | |
GET /entityset(key)/ action | /Products(1)/Rate | ActionNameOnEntityType or ActionName | RateOnProduct |
GET /entityset(key)/cast/ action | /Products(1)/Models.Book/CheckOut | ActionNameOnEntityType or ActionName | CheckOutOnBook |
更多信息查看:OData Actions.
-
方法签名(Action Signatures)
这里有方法签名的几个规则:
-
如果路径中包括主键,action中必须有一个以 主键为名的参数。
-
如果路径中包括导航属性中的主键(即关系entity的外键),action中必须有一个以 外键为名的参数。
-
用 [FromODataUri]参数来修饰 主键和 外键参数。
-
POST 和 PUT 请求需要有entity中的一个参数。
-
PATCH请求需要 Delta类型参数,其中 T是entity类型
下面是每个内置Odatal路由约定的方法签名的例子:
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)
}
自定义路由约定
可以通过实现IODataRoutingConvention接口来自定义路由约定,以添加其他可能OData URIs。该接口有两个方法:
string SelectController(ODataPath odataPath, HttpRequestMessage request);//返回Controller名字
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext,
ILookup<string, HttpActionDescriptor> actionMap);//返回Action名字
两个方法,当该约定不应用于某个请求,则都是返回null。
直接给出一个例子吧:/odata/Products(1)/Suppliers(1)
以下是自定义约定来实现的query。
using System.Web.OData.Routing;
using System.Web.OData.Routing.Conventions;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Controllers;
using Microsoft.OData.Edm;
namespace ODataRouting
{
public class NavigationIndexRoutingConvention : EntitySetRoutingConvention
{
//返回Action名字
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”。
- action名字是“Get{EntityType}”,其中{EntityType}是导航属性集合中类型。如:GetSupplier。你可以使用任何命名约定,只要Controller名字匹配上。
- action有key和relatekey两个参数。(ODataRouteConstants.)
下一步将新约定加入到路由约定。(WebApiConfig.cs)
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);
}
}
}
其他有用相关例子:
- CompositeKeyRoutingConvention
- CustomNavigationRoutingConvention
- NonBindableActionRoutingConvention
- ODataVersionRouteConstraint
- source code