ASP.NET Web API 2 Odata のルーティング規則
この記事では、SP.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
には次の 3 つのセグメントがあります。
Products
は、"Products" という名前のエンティティ セットを参照します。1
はエンティティ キーであり、セットから 1 つのエンティティを選択します。Supplier
は、関連エンティティを選択するナビゲーション プロパティです。
そのため、このパスは製品 1 のサプライヤーを選択します。
Note
OData パス セグメントが URI セグメントに常に対応するとは限りません。 たとえば、"1" はパス セグメントと見なされます。
コントローラー名。 コントローラー名は、常にリソース パスのルートにあるエンティティ セットから派生します。 たとえば、リソース パスが /Products(1)/Supplier
である場合、Web API は ProductsController
という名前のコントローラーを検索します。
アクション名。 アクション名は、次の表に示すように、パス セグメントとエンティティ データ モデル (EDM) から派生します。 場合によっては、アクション名に 2 つの選択肢があります。 たとえば、"Get" や "GetProducts" などです。
エンティティの照会
要求 | 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 エンドポイントを作成する」を参照してください。
エンティティの作成、更新、削除
要求 | 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 |
ナビゲーション プロパティの照会
要求 | URI の例 | アクション名 | アクションの例 |
---|---|---|---|
GET /entityset(key)/navigation | /Products(1)/Supplier | 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/Suppliers(1) | DeleteLink |
詳細については、「エンティティ関係の操作」を参照してください。
Properties
Web 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 アクション」を参照してください。
メソッドのシグネチャ
メソッド シグネチャの規則をいくつか次に示します。
- パスにキーが含まれている場合、アクションには 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)
}
カスタム ルーティング規則
現在、組み込みの規則では、考えられるすべての OData URI がカバーされているわけではありません。 IODataRoutingConvention インターフェイスを実装することで、新しい規則を追加できます。 このインターフェイスには、次の 2 つのメソッドが含まれています。
string SelectController(ODataPath odataPath, HttpRequestMessage request);
string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext,
ILookup<string, HttpActionDescriptor> actionMap);
- SelectController はコントローラーの名前を返します。
- SelectAction はアクションの名前を返します。
どちらのメソッドでも、その要求に規則が適用されない場合、メソッドは null を返す必要があります。
ODataPath パラメーターは、解析された OData リソース パスを表します。 これには、リソース パスのセグメントごとに 1 つずつ、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;
}
}
}
注:
- そのクラスの SelectController メソッドがこの新しいルーティング規則に適しているため、EntitySetRoutingConvention から派生します。 つまり、SelectController を再実装する必要はありません。
- この規則は GET 要求にのみ適用され、パス テンプレートが "~/entityset/key/navigation/key" の場合にのみ適用されます。
- アクション名は "Get{EntityType}" です。この場合、{EntityType} はナビゲーション コレクションの型です。 たとえば、"GetSupplier" などです。 任意の名前付け規則を使用できます。コントローラー アクションが一致していることを確認してください。
- アクションは、key と relatedKey という名前の 2 つのパラメーターを受け取ります。 (事前定義済みのパラメーター名の一覧については、「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 名前空間で定義されています。