ルーティングの設定を行い、URLを柔軟に定義しよう。ASP.NET Web APIでは検証/例外処理はどのように行うべきだろうか?
本連載は、ASP.NET Web APIを基礎から解説している。第4回目となる本稿で、基礎情報の紹介は終了とする。前回は、ASP.NET Web APIにおいて最低限必要となる実装である「APIコントローラーの実装」を解説した。今回は、まず初めに「ルーティングの設定」をマスターし、URLの定義を行おう。
言語はC#、本稿で対象とするASP.NET Web APIのバージョンは、1と2の両方とし、違いがある場合は随時補足する。
ルーティングとは、HTTPリクエストのURLに応じて、呼び出す処理を決定する仕組みのことだ。ASP.NET MVCから登場し、今やASP.NET Webフォームにも組み込まれている。開発者は、ルーティングの設定を行うことで、URLを柔軟に決定できる。Web APIを提供するASP.NET Web APIとしてはとても重要な仕組みだ。
ASP.NET Web APIのルーティングの設定について学ぶ前に、把握しておきたいことがある。それは、ルーティングのメカニズム――HTTPリクエストはどのようにしてアクションメソッドにマッピングされるか――である。ASP.NET MVCでは特に意識することはないのだが、ASP.NET Web APIのそれは若干のクセがある。ルーティングのメカニズムを把握した上でルーティングの設定を行おう。
ASP.NET Web APIではアクションメソッドの選択時、ルーティングの設定の他に次の2つの条件を確認する。
HTTPリクエストとアクションメソッドがこれらの条件に合わないと、いくらルーティングの設定を行っても思うようにマッピングされないので注意が必要だ。それぞれの条件について詳しく見てみよう。
1. HTTPメソッドによる条件
1つ目の条件は、アクションメソッドのメソッド名が、HTTPメソッド(Get/Post/Put/Delete/Head/Options/Patch)で始まるか、もしくはHttpPost属性などの属性が付与されていることだ(ただし、例外が存在するので後述する)。
HTTPリクエスト側 | アクションメソッド側 | |
---|---|---|
確認項目 | HTTPメソッド | メソッド名、または属性の付与 |
表1 1つ目の条件で確認される項目 |
例えば、次のリスト1のようなAPIコントローラーとアクションメソッドが定義されている場合、「GET ~/api/customers/」でアクセスするとGetAllメソッドが、「PUT ~/api/customers/」でアクセスするとUpdateCustomerメソッドが呼び出される(デフォルトのルーティング設定が適用されているものとする)。
public class CustomersController : ApiController
{
// GET ~/api/cutstomers
public string GetAll();
// PUT ~/api/cutsomers
[HttpPut]
public void UpdateCustomer();
// POST ~/api/cutsomers
public string Hoge();
}
リスト1に記述されている「[HttpPut]」といった属性は、HTTPメソッドを表す。このような属性クラスとしては、HttpGet/HttpPost/HttpPut/HttpDelete属性などが使用できる*1(全てSystem.Web.Http名前空間に配置されている*2)。アクションメソッド名がHTTPメソッドで始まらない場合は、これらHTTPメソッドを表す属性を付与しなければならない。
ただし例外として、HTTPメソッドがPOSTの場合は、これらの条件は無視される(アクションメソッド名がPostで始まる必要もなく、HttpPost属性を付与する必要もない)。つまり、「POST ~/api/customers/」でアクセスすると、リスト1のHogeメソッドが呼び出される。
*1 AcceptVerbs属性で「[AcceptVerds("Get")]」といったようにHTTPメソッドを文字列で指定することも可能だ。
*2 ASP.NET MVCのSystem.Web.Mvc名前空間に同じ名前の属性があるので間違えないように注意。
2. URLのパラメーターによる条件
2つ目の条件は、アクションメソッドの引数の型がプリミティブ型*3である場合、HTTPリクエストのURLに、引数と同じ名前のパラメーター(クエリ文字列、またはURLの一部)が定義されていなければならないことだ。
*3 プリミティブ型(参照:Type.IsPrimitive プロパティ)の引数
HTTPリクエスト側 | アクションメソッド側 | |
---|---|---|
確認項目 | クエリ文字列、またはURLの一部 | メソッドの引数(プリミティブ型) |
表2 2つ目の条件で確認される項目 |
文章だと理解しにくいのでコードを見てほしい。例えば下のリスト2のようにアクションメソッドが定義されている場合、「GET ~/api/customers?name=Taro」のようにクエリ文字列が指定されていないとマッピングされない。または、ルーティングの設定のURLテンプレートで「api/customers/{name}」のように「name」のパラメーターが定義されている必要がある。
public class CustomersController : ApiController
{
//GET ~/api/customers/?name=Taro
//または
//GET ~/api/customers/{name} など
public string Get(string name);
}
また、できるだけ多くのパラメーターと一致する引数が定義されるアクションメソッドが優先して呼び出される。例えば、下のリスト3のようにアクションメソッドが定義されている場合、「GET ~/api/customers/?name=taro」はGet(string name)メソッドにマッピングされ、「GET ~/api/cutomers/?name=taro&number=2」は、Get(string name, string number)メソッドにマッピングされる(パラメーターの順序や大文字小文字は無視される)。
public class CustomersController : ApiController
{
//GET ~/api/customers?name=taro&number=1
public string Get(string name, string number);
//GET ~/api/customers?name=taro
public string Get(string name);
}
リスト3の例は、新たにルーティングの設定を追加する必要がなく、それぞれのアクションメソッドにマッピングできることが利点だ。
反対に、リスト2と3で「GET ~/api/customers/」の場合はどうなるかというと、アクションメソッドに定義されている引数に該当するパラメーターがないため、マッピングされない。もしマッピングさせる場合は、下のリスト4のGet(string name)メソッドのように初期値を設定する必要がある(初期値は、ルーティングの設定でも行うことができる)。
public class CustomersController : ApiController
{
public string Get(string name, string number);
//GET ~/api/customers
public string Get(string name = "anonymous");
}
以上2つの条件が、ASP.NET Web API特有のマッピングの条件である。ルーティングの設定とは違い、変更するには拡張が必要なので注意が必要だ。
それでは上記の条件を踏まえて、ルーティングの設定を行っていこう。概要は第1回の節「ルーティングの設定」ですでに説明しているので、ここでは特筆すべき事項を記載する。
下のリスト5は、デフォルトのルーティングの設定に1つルーティングを追加した例だ。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "UserProfileApi",
routeTemplate: "api/profile/{name}",
defaults: new { controller = "UserProfile", name = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//...(省略)
ルーティングの設定は、MapHttpRoute拡張メソッド(System.Web.Http名前空間)により、任意の数だけルーティングを追加できる。追加されたルーティングは登録した順に、HTTPリクエストのURLと一致するURLテンプレートがないか照会される。一致した場合、該当のル―ティング設定の情報を基に、APIコントローラーとアクションメソッドが特定される。
リスト5の例を基に、MapHttpRoute拡張メソッドの引数について説明したものが次の表3となる。
引数名 | 引数の説明 |
---|---|
name | ルートの名前を定義する。任意の値でよいが、他のルートと重複しないよう一意の名前を定義する必要がある |
routeTemplate | マッピングしたいURLのパターンを記述する。プレイスホルダーで指定した部分はパラメーターとして扱われる |
defaults | パラメーターの初期値を設定する。RouteParameter.Optionalはパラメーターの存在が必須ではないことを表している |
表3 ルーティングの設定を行うMapHttpRoute拡張メソッドの引数とその説明 |
パラメーター「controller」は、APIコントローラーの特定に必要なため、引数routeTemplateまたはdefaultsのどちらかで必ず指定する必要がある。また、リスト5には記述されていないが、パラメーターは他にアクションを指定する「action」がある。このアクションについては次の節で解説する*4。
また、ルーティングの設定で定義したパラメーターは、アクションメソッドの引数に同じ名前で定義することで、パラメーターに該当するURLの一部の値を、アクションメソッドで取得できる(参考「第3回 APIコントローラーの実装方法 HTTPリクエストを取得する」)
*4 ルーティングの設定方法は、ASP.NET MVCとほぼ同じであるため、詳しい設定方法については「[ASP.NET MVC]ルート定義を追加するには?[3.5、4、C#、VB]」を参考にしてほしい(ただし、ASP.NET MVCとASP.NET Web APIのルーティングの設定は、登録先のオブジェクトが異なるので注意。ASP.NET Web APIでは、HttpConfigurationクラスにルーティングの設定情報を登録している)。
アクションを指定する
ASP.NET Web APIでは、デフォルトのルーティングの設定だけでは不十分で、アクションの指定が必要な場合がある。
例えば次のリスト6のように、同じAPIコントローラー内に複数のHTTPメソッドに対応したアクションメソッドを定義した場合、とあるHTTPリクエストを送信するとルーティング設定のエラーが発生してしまう。理由は、「GET ~/api/cutomers」にマッピングされるアクションメソッドが複数あるためだ。
public class CustomerController : ApiController
{
public string GetName();
public string GetNumber();
}
この場合は、アクション(パラメーター名「action」)に、アクションメソッド名を指定したルーティングの設定を追加することで、正しくマッピングできる。次のリスト7は、リスト6に定義されたアクションメソッドを異なるURLでマッピングさせるための設定例だ。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "CustomerNameApi",
routeTemplate: "api/customers/name",
defaults: new { controller = "Customers", action = "GetName" }
);
config.Routes.MapHttpRoute(
name: "CustomerNumberApi",
routeTemplate: "api/customers/number",
defaults: new { controller = "Customers", action = "GetNumber" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
アクションに設定する値は、必ずしもアクションメソッド名である必要はない。リスト8のように ActionName属性(=System.Web.Http名前空間のActionNameAttributeクラス)を付与すれば、ルーティング設定でアクションメソッド名ではなくActionName属性の引数に設定した値を使用できる。
public class CustomerController : ApiController
{
[ActionName("name")]
public string GetName();
}
以上紹介したアクションの設定はほんの一例だ。APIコントローラーに大量のAPIを用意する場合やRESTfulなURL設計を行わない場合、恐らくアクション名の指定が必要になるであろう。だが、先ほどの例のように1つ1つルーティングを設定するのは面倒である。この場合は、デフォルトのルーティングの設定をアクションベースにする方法(リスト9を参照)や、ASP.NET Web API 2から提供されている属性ルーティング(リスト10を参照)を使用する方法を採ることで解決できる。
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
[RoutePrefix("api/customers/{customerId}")]
public class OrdersController : ApiController
{
// ~/api/customers/{customerId}/orders
[Route("orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId);
// ~/api/customers/{customerId}/orders/{orderId}
[Route("orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId);
}
ルーティングの設定の解説は以上だ。次は、HTTPリクエストの内容を検証する方法について解説する。
Copyright© Digital Advantage Corp. All Rights Reserved.