Help us understand the problem. What is going on with this article?

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

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

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

前提

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

NGケース

上記前提のもと、下記のように実装した場合…

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";
        }
    }
}
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クラスが特定できない旨の実行時エラーとなってしまう。

/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ケース

策としては、エンドポイントを変える、または諦めて以下のように同一クラス内に記述すること。

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)
        {
            // 更新処理の呼び出し
        }
    }
}

このような挙動を示す理由(予想)

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

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は奥深い。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした