読者です 読者をやめる 読者になる 読者になる

miso_soup3 Blog

主に ASP.NET 関連について書いています。

ASP.NET WEB API ルーティングについていろいろ

ルーティング基本

WEB API において、アクションメソッドへのマッピングの基本は、
HTTP メソッド名から始まるメソッドへマッピングされることです。

HTTP POST だったら、POST..から始まるメソッドへ、
HTTP GET だったら、GET...から始まるメソッドへマッピングされます。
例↓

public class ValuesController : ApiController
{
    // GET ~/api/values/
    public void Get() { }

    // POST ~/api/values/
    public void Post() { }

    // PUT ~/api/values/
    public void Put() { }
}

後ろの名前はなんでもOK

HTTP メソッド名から始まれば、後ろの名前はなんでもOKです。
GetHogeHoge という名前のメソッドでも、
GET ~/api/{controller名}/ でマッピングされます。
(※ MVC のように ~/api/{controller名}/HogeHoge ではマッピングされないので注意)

public class ProductsController : ApiController
{
    // GET ~/api/products/
    public String GetHogeHoge() { }
}

アクションメソッド属性をつける

メソッド名が、HTTP メソッド名から始まらなくても
[HttpGet] や、[HttpPost] 等の属性をつければマッピング可能です。

例えば、FindHoge というメソッド名でも、[HttpGet] という属性をつければ、
GET ~/api/{controller名}/ でマッピングされます。

public class ProductsController : ApiController
{
    // GET ~/api/products/
    [HttpGet]
    public String FindHoge() { }
}

次に重要なのは、アクションメソッドの引数

アクションメソッドのマッピングは、引数も重要な条件になります。

下の例の様に、メソッドが、
GetHoge(string name); の1つしかない場合、
GET ~/api/products/ はマッピングはされません。
GET ~/api/products/?name=taro だとマッピングされます。

public class ProductsController : ApiController
{
    // 誤:GET ~/api/products/
    // 正:GET ~/api/products/?name={0}
    [HttpGet]
    public String GetHoge(string name) { }
}

ちなみに、誤である GET ~/api/products/ を実行すると、
404 Not Found で下のようなレスポンスが返ってきます。

もし、GetHoge(); というメソッドが追加されれば、
GET ~/api/products/ は GetHoge(); へ、
GET ~/api/products/?name={0} は、GetHoge(String name); へ
マッピングされます。

極端にすると、下の例のように
メソッド名は無視したオーバーロードのようなマッピングになります。

public class ProductsController : ApiController
{
    // GET ~/api/products/
    public String GetHoge() { }

    // GET ~/api/products/?name={0}
    public String GetHogeHoge(String name) { }

    // GET ~/api/products/?message={1}
    public String GetHogeHogeHoge(String message) { }
}

引数の名前も条件に

引数(パラメータ)の名前によっても、マッピングは変わってきます。

下の例の用に、GET でマッピングされるメソッドが2つあり、
引数の名前が同じ場合、GET ~/api/products/?name={0} のマッピングは失敗します。
GET と 引数でメソッドを選択するため、どちらのメソッドにも当てはまってしまうからです。

public class ProductsController : ApiController
{
    // GET ~/api/products/?name={0} は下の2つにはマッピングされない

    public String GetHoge(String name) { }
    public String GetHogeHoge(String name) { }
}

ちなみに、このときのレスポンスは、
500 Internal Server Error で下のようなレスポンスが返ってきます。

アクション名を指定する

上記の場合で、どうしてもマッピングを振り分けたい場合は、
お馴染みのルーティング設定で、アクション名を指定します。

下の例のように、それぞれのURL でマッピングを定義する場合は・・・

public class ProductsController : ApiController
{
    // GET ~/api/products/hoge?name={0} 
    public String GetHoge(String name) { }

    // GET ~/api/products/hogehoge?name={0} 
    public String GetHogeHoge(String name) { }
}

WebApiConfig.cs にて、このようにルーティングを設定します。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //追加
        config.Routes.MapHttpRoute(
            name: "GetHogeHoge",
            routeTemplate: "api/products/hogehoge",
            defaults: new { controller = "products", action = "GetHogeHoge" }
        );

        //追加
        config.Routes.MapHttpRoute(
            name: "GetHoge",
            routeTemplate: "api/products/hoge",
            defaults: new { controller = "products", action = "GetHoge" }
        );

        //デフォルトのルーティング
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

アクション名を指定すれば、振り分けは可能ですが、
やりすぎてしまうと、恐らく RESTFul から離れてしまうのではないかなと思います。

まとめ

WEB API のアクションメソッドのマッピングは、
以下のものが重要になります。

  • HTTP メソッド
  • ルーティングでのアクション名の指定
  • パラメータ

先に挙げたオーバーロードっぽいものもあるため、
ちゃんとした RESTFul な設計でないと、簡単にマッピングが壊れてしまう気がします…。

壊れないようにするためには、テストを用意すればいいですが、
簡単に確認する方法があります。

ヘルプページ自動生成パッケージにて、表示されるAPI一覧には、
マッピングに失敗した API は表示されないようになっているので、お手軽に確認できます。