#ASP.NET WebAPIで「同一URL・HTTPメソッド違い」のControllerが実行時エラーとなる場合の対処法
ASP.NETのWebAPI Frameworkでは、Route Attributeを用いて、エンドポイントマッピングができる機能がある。
しかし同一エンドポイントで複数APIを実装しようとした際にハマったのでメモ。
###前提
・できるだけ「1つのコントロールクラスに1つのアクションメソッド」としたい。
・メンバー情報の取得(GET)、更新(PUT)という2つの機能を実装する
##NGケース
上記前提のもと、下記のように実装した場合…
using System.Web.Http;
namespace ExampleWebApi.Controller
{
/// <summary>
/// メンバー情報取得コントローラ
/// </summary>
public class MemberFindController : ApiController
{
/// <summary>
/// メンバー情報取得コントローラ
/// </summary>
/// <param name="id">メンバーID</param>
/// <returns>メンバー情報文字列</returns>
[Route("api/me/{id}")]
[HttpGet]
public string MemberFind(int id)
{
// 何らかの取得処理の呼び出し
return "ExampleMemberInfo";
}
}
}
using System.Web.Http;
namespace ExampleWebApi.Controller
{
/// <summary>
/// メンバー情報更新コントローラ
/// </summary>
public class MemberUpdateController : ApiController
{
/// <summary>
/// メンバー情報更新コントローラ
/// </summary>
/// <param name="id">メンバーID</param>
[Route("api/me/{id}")]
[HttpPut]
public void MemberUpdate(int id, [FromBody] string value)
{
// 更新処理の呼び出し
}
}
}
HTTPメソッド定義で一意となっているにもかかわらず、Controllerクラスが特定できない旨の実行時エラーとなってしまう。
{
"Message": "エラーが発生しました。",
"ExceptionMessage": "URL に一致する複数のコントローラー型が見つかりました。これは、複数のコントローラー型の属性ルートが要求された URL に一致する場合に発生する可能性があります。¥r¥n¥r¥n要求によって、次の一致するコントローラー型が見つかりました: ¥r¥nAilsApi.Controllers.MemberUpdateController¥r¥nAilsApi.Controllers.MemberFindController",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": " 場所 System.Web.Http.Dispatcher.DefaultHttpControllerSelector.GetDirectRouteController(IHttpRouteData routeData)¥r¥n 場所 System.Web.Http.Dispatcher.DefaultHttpControllerSelector.SelectController(HttpRequestMessage request)¥r¥n 場所 System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}
##OKケース
策としては、エンドポイントを変える、または諦めて以下のように同一クラス内に記述すること。
using System.Web.Http;
namespace ExampleWebApi.Controller
{
/// <summary>
/// メンバー情報操作コントローラ
/// </summary>
public class MemberController : ApiController
{
/// <summary>
/// メンバー情報取得コントローラ
/// </summary>
/// <param name="id">メンバーID</param>
/// <returns>メンバー情報文字列</returns>
[Route("api/me/{id}")]
[HttpGet]
public string MemberFind(int id)
{
// 何らかの取得処理の呼び出し
return "ExampleMemberInfo";
}
/// <summary>
/// メンバー情報更新コントローラ
/// </summary>
/// <param name="id">メンバーID</param>
[Route("api/me/{id}")]
[HttpPut]
public void MemberUpdate(int id, [FromBody] string value)
{
// 更新処理の呼び出し
}
}
}
##このような挙動を示す理由(予想)
上述の謎な挙動ではあるが、全く理由が思い至らないわけでもない。
例として、同じロジックを書くにも以下のような書き方もある。
using System.Web.Http;
namespace ExampleWebApi.Controller
{
/// <summary>
/// メンバー情報操作コントローラ
/// </summary>
public class MeController : ApiController
{
/// <summary>
/// メンバー情報取得コントローラ
/// </summary>
/// <param name="id">メンバーID</param>
/// <returns>メンバー情報文字列</returns>
public string Get(int id)
{
// 何らかの取得処理の呼び出し
return "ExampleMemberInfo";
}
/// <summary>
/// メンバー情報更新コントローラ
/// </summary>
/// <param name="id">メンバーID</param>
public void Put(int id, [FromBody] string value)
{
// 更新処理の呼び出し
}
}
}
Controllerクラス名でルーティングを、アクションメソッド名でHTTPメソッドを表現する記法である。
この記法だと当然「HTTPメソッド違いは同一クラス内に」という制限が発生するわけだが、この制限のみがAttributeを使用したルーティングにも何故か適用されているものと考えられる。
Attributeによる柔軟なルーティング指定を実現させているはずなのに、同一クラスの制限をもたせる必要性はないと思われるのだが… .NETは奥深い。