事前にお詫び申し上げます。この投稿はあなたの質問から少し外れていますが、あなたの質問を読んだときに、このすべてが泡立ちました。
WebAPIマッチングセマンティックWebAPI
(のデフォルトルート)で使用されるマッチングセマンティクスは非常に単純です。
- アクションの名前を動詞と一致させます(動詞= GET?「get」で始まるメソッド名を探します)
- パラメータが渡された場合、APIはパラメータを使用してアクションを探します
したがって、コードサンプルでは、パラメーターのないGETリクエストは、パラメーターのGet*( )
ない関数と一致します。含むGetとIDはを探しますGet***(int id)
。
例
マッチングセマンティクスは単純ですが、MVC開発者(少なくともこの開発者)にとっては混乱を招きます。いくつかの例を見てみましょう:
奇数名-getメソッドは、「get」で始まる限り、任意の名前を付けることができます。したがって、ウィジェットコントローラーの場合は、関数GetStrawberry()
に名前を付けることができますが、それでも一致します。マッチングは次のようなものと考えてください。 methodname.StartsWith("Get")
複数のマッチングメソッド-パラメータのないGetメソッドが2つある場合はどうなりますか? GetStrawberry()
およびGetOrange()
。私が知る限り、コードで最初に定義された関数(ファイルの先頭)が...strangeに勝ちます。これには、コントローラーの一部のメソッドに到達できなくなるという副作用があります(少なくともデフォルトルートでは)...見知らぬ人。
注:ベータ版は「複数の方法を一致させる」ために上記のように動作しました-RC&リリースバージョンはもう少しOCDです。一致する可能性のあるものが複数ある場合は、エラーがスローされます。この変更により、複数のあいまいな一致の混乱がなくなります。同時に、順序と重複するルートに依存して、同じコントローラー内でRESTスタイルとRPCスタイルのインターフェイスを混在させる機能が低下します。
何をすべきか?
ええと、WebAPIは新しく、コンセンサスはまだ合体しています。コミュニティはRESTの原則にかなり手を伸ばしているようです。ただし、すべてのAPIがRESTfulである、またはそうである必要があるわけではありません。一部のAPIは、RPCスタイルでより自然に表現されます。RESTと人々がRESTと呼ぶものは、少なくともRoy Fieldingにとっては、かなり の混乱の原因のようです。
実用主義者として、私は多くのAPIが70%RESTfulであり、RPCスタイルのメソッドがわずかにあると思います。まず、コントローラーの急増だけで(webapiバインディングメソッドが与えられた場合)、開発者は大騒ぎになります。第2に、WebAPIには、APIパスのネストされた構造を作成するための組み込みの方法が実際にはありません(つまり /api/controller/
、簡単ですが、/api/CATEGORY/Sub-Category/Controller
実行可能ですが、面倒です)。
私の観点からは、webAPIフォルダー構造がデフォルトのAPIパスを制御することを望んでいます...つまり、UIプロジェクトでCategoryフォルダーを作成する/api/Category
と、デフォルトのパスになります(このMVCの記事と同様のことです)。
私は何をしましたか?
したがって、いくつかの要件がありました。(1)ほとんどの場合、RESTful構文を使用できるようにする、(2)コントローラーを「名前空間」で分離する(サブフォルダーを考える)、(3)追加のrpcを呼び出すことができる-必要に応じてメソッドのように。これらの要件の実装は、巧妙なルーティングに帰着しました。
// SEE NOTE AT END ABOUT DataToken change from RC to RTM
Route r;
r = routes.MapHttpRoute( name : "Category1",
routeTemplate : "api/Category1/{controller}/{id}",
defaults : new { id = RouteParameter.Optional } );
r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category1"};
r = routes.MapHttpRoute( name : "Category2",
routeTemplate : "api/Category2/{controller}/{id}",
defaults : new { id = RouteParameter.Optional } );
r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category2"};
routes.MapHttpRoute( name : "ApiAllowingBL",
routeTemplate : "api/{controller}/{action}/{id}",
defaults : new { id = RouteParameter.Optional } );
routes.MapHttpRoute( name : "DefaultApi",
routeTemplate : "api/{controller}/{id}",
defaults : new { id = RouteParameter.Optional } );
- 最初の2つのルートは、「サブフォルダー」ルートを作成します。サブフォルダごとにルートを作成する必要がありますが、主要なカテゴリに限定したため、最終的には3〜10個になります。これらのルートがデータトークンを追加し
Namespace
て、特定のルートで検索されるクラスを制限する方法に注目してください。これは、UIプロジェクトにフォルダーを追加するときの一般的な名前空間の設定にうまく対応しています。
- 3番目のルートでは、特定のメソッド名を呼び出すことができます(従来のMVCのように)。Web APIはURL内のアクション名を廃止するため、どの呼び出しがこのルートを必要としているかを比較的簡単に判断できます。
- 最後のルートエントリは、デフォルトのWebAPIルートです。これは、すべてのクラス、特に私の「サブフォルダー」の外にあるクラスをキャッチします。
別の言い方
をすれば、私の解決策は、コントローラーをもう少し分離することでしたので、/api/XXXX
混雑しすぎませんでした。
- UIプロジェクト(たとえば
Category1
)にフォルダーを作成し、そのフォルダー内にAPIコントローラーを配置します。
- Visual Studioは、フォルダーに基づいてクラスの名前空間を自然に設定します。したがって
Widget1
、Category1
フォルダ内のデフォルトの名前空間はUI.Category1.Widget1
。です。
- 当然、API URLにフォルダー構造を反映させたいと思いました(
/api/Category1/Widget
)。/api/Category1
上記の最初のマッピングは、ルートにハードコーディングすることによりnamespace
、一致するコントローラーを検索するクラスをトークンが制限することを実現します。
注:リリースの時点では、DataTokens
デフォルトではnullです。これがバグなのか機能なのかわかりません。そこで、少しヘルパーメソッドを作成して、RouteConfig.cs
ファイルに追加しました。
r.AddRouteToken("Namespaces", new string[] {"UI.Controllers.Category1"});
private static Route AddRouteToken(this Route r, string key, string[] values) {
//change from RC to RTM ...datatokens is null
if (r.DataTokens == null) {
r.DataTokens = new RouteValueDictionary();
}
r.DataTokens[key] = values;
return r;
}
注2:@Jamie_Ideがコメントで指摘しているように、これはWebAPI 1の投稿であるとIHttpRoute.DataTokens
はいえ、セッターがないため、上記のソリューションはWebAPI2では機能しません。これを回避するには、次のような単純な拡張方法を使用できます。
private static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, string[] namespaceTokens)
{
HttpRouteValueDictionary defaultsDictionary = new HttpRouteValueDictionary(defaults);
HttpRouteValueDictionary constraintsDictionary = new HttpRouteValueDictionary(constraints);
IDictionary<string, object> tokens = new Dictionary<string, object>();
tokens.Add("Namespaces", namespaceTokens);
IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: tokens, handler:null);
routes.Add(name, route);
return route;
}