共用方式為


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 包含:

  • 服務根目錄
  • 資源路徑
  • 查詢選項

顯示 OData 路由約定的螢幕擷取畫面,從左到右顯示服務根目錄、資源路徑和查詢選項。

對於路由來說,重要的部分是資源路徑。 資源路徑被分為多個段落。 例如,/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] 參數修飾 keyrelatedKey 參數。
  • 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 的實作會執行以下動作:

  1. 比較路徑範本以查看此慣例是否適用於目前請求。 如果不適用,則傳回 Null。
  2. 如果慣例適用,請使用 ODataPathSegment 執行個體的屬性來衍生控制器和動作名稱。
  3. 對於動作,將任何值新增至應繫結到動作參數通常是實體索引鍵) 的路由字典中。

我們來看一個具體的例子。 內建路由慣例不支援對導覽集合進行索引。 換句話說,沒有如下所示的 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;
        }
    }
}

注意:

  1. 我從 EntitySetRoutingConvention 衍生,因為該類別中的 SelectController 方法適合這個新的路由慣例。 這代表我不需要重新實作 SelectController
  2. 此慣例僅適用於 GET 請求,且僅當路徑範本為「~/entityset/key/navigation/key」時。
  3. 動作名稱為「Get{EntityType}」,其中 {EntityType} 是導覽集合的類型。 例如,「GetSupplier」。 您可以使用您喜歡的任何命名慣例 - 只需確保您的控制器動作匹配即可。
  4. 動作採用兩個名為 keyrelatedKey 的參數。 (有關一些預先定義參數名稱的清單,請參閱 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 命名空間中定義。