ASP.NET WebAPIで「同一URL・HTTPメソッド違い」のControllerが実行時エラーとなる場合の対処法

#ASP.NET WebAPIで「同一URL・HTTPメソッド違い」のControllerが実行時エラーとなる場合の対処法

ASP.NETのWebAPI Frameworkでは、Route Attributeを用いて、エンドポイントマッピングができる機能がある。
しかし同一エンドポイントで複数APIを実装しようとした際にハマったのでメモ。

###前提
・できるだけ「１つのコントロールクラスに1つのアクションメソッド」としたい。
・メンバー情報の取得(GET)、更新(PUT)という2つの機能を実装する

##NGケース
上記前提のもと、下記のように実装した場合…

```c#:MemberFindController.cs
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";
        }
    }
}
```

```c#:MemberUpdateController.cs
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クラスが特定できない旨の実行時エラーとなってしまう。

```json:/api/me/1
{
    "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ケース
策としては、エンドポイントを変える、または諦めて以下のように同一クラス内に記述すること。

```c#:MemberController.cs
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)
        {
            // 更新処理の呼び出し
        }
    }
}
```

##このような挙動を示す理由（予想）


上述の謎な挙動ではあるが、全く理由が思い至らないわけでもない。
例として、同じロジックを書くにも以下のような書き方もある。

```c#:MeController.cs
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は奥深い。