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」。
查詢實體
Request | 範例 URI | 動作名稱 | 範例動作 |
---|---|---|---|
GET /entityset | /Products | GetEntitySet 或 Get | GetProducts |
GET /entityset(key) | /Products(1) | GetEntityType 或 Get | GetProduct |
GET /entityset(key)/cast | /Products(1)/Models.Book | GetEntityType 或 Get | GetBook |
有關詳細資訊,請參閱建立「唯讀 OData 端點」。
建立、更新和刪除實體
Request | 範例 URI | 動作名稱 | 範例動作 |
---|---|---|---|
POST /entityset | /Products | 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 |
查詢導覽屬性
Request | 範例 URI | 動作名稱 | 範例動作 |
---|---|---|---|
GET /entityset(key)/navigation | /Products(1)/Supplier | GetNavigationFromEntityType 或 GetNavigation | GetSupplierFromProduct |
GET /entityset(key)/cast/navigation | /Products(1)/Models.Book/Author | GetNavigationFromEntityType 或 GetNavigation | GetAuthorFromBook |
有關更多資訊,請參閱「使用實體關係」。
建立和刪除連結
Request | 範例 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/Suppliers(1) | DeleteLink |
有關更多資訊,請參閱「使用實體關係」。
屬性
需要 Web API 2
Request | 範例 URI | 動作名稱 | 範例動作 |
---|---|---|---|
GET /entityset(key)/property | /Products(1)/Name | GetPropertyFromEntityType 或 GetProperty | GetNameFromProduct |
GET /entityset(key)/cast/property | /Products(1)/Models.Book/Author | GetPropertyFromEntityType 或 GetProperty | GetTitleFromBook |
動作
Request | 範例 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 動作」。
方法簽章
以下是方法簽章的一些規則:
- 如果路徑包含索引鍵,則動作應該有一個名為 key 的參數。
- 如果路徑包含導覽屬性的索引鍵,則該動作應具有名為 relatedKey 的參數。
- 使用 [FromODataUri] 參數修飾 key 和 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 命名空間中定義。