ASP.NET Web API におけるルーティングとは、受信した HTTP リクエストを特定のコントローラーのアクションに結びつける仕組みのことです。Web API 2 では、従来の「デフォルトルーティング(規約ベース)」に加え、属性ルーティング(Attribute Routing)が導入されました。この手法では、C# の属性を使用してアクションメソッドに直接パスを定義するため、URI 構造をより柔軟かつ直感的に設計できます。
属性ルーティングを採用するメリット
従来の規約ベースのルーティングでは、WebApiConfig.cs などに一括してルールを記述していましたが、リソースが階層化されるにつれて定義が複雑化する課題がありました。属性ルーティングを使用すると、以下のような複雑な URI パターンを容易に実装できます。
- リソースの階層化:
/authors/1/booksのような親子関係の表現 - API のバージョン管理:
/api/v1/productsと/api/v2/productsの切り分け - 多様なパラメータ型: 同じパスで ID(数値)とユーザー名(文字列)を判別
属性ルーティングの有効化
属性ルーティングを利用するには、アプリケーションの起動設定(通常は WebApiConfig.cs)で MapHttpAttributeRoutes メソッドを呼び出します。
using System.Web.Http;
namespace MyProject
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// 属性ルーティングの有効化
config.MapHttpAttributeRoutes();
// 従来の規約ベースのルーティングと併用も可能
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
基本的なルーティングの定義
コントローラーのアクションに [Route] 属性を付与することで、パスを指定します。波括弧 {} で囲まれた部分は、メソッドの引数としてバインドされます。
public class LibraryController : ApiController
{
[Route("categories/{categoryName}/items")]
[HttpGet]
public IEnumerable<Item> GetItemsByCategory(string categoryName)
{
// 実装コード
return items;
}
}
HTTP メソッドの指定
デフォルトではメソッド名の接頭辞に基づいて判断されますが、属性を使用して明示的に制御することが推奨されます。
[HttpGet][HttpPost][HttpPut][HttpDelete]
ルートプレフィックス(Route Prefix)
コントローラー内のすべてのアクションに共通するパスのプレフィックスがある場合、[RoutePrefix] 属性を使用して共通化できます。
[RoutePrefix("api/v1/members")]
public class MembersController : ApiController
{
// GET api/v1/members/all
[Route("all")]
public IEnumerable<Member> GetAll() { ... }
// GET api/v1/members/detail/5
[Route("detail/{id:int}")]
public Member GetById(int id) { ... }
// ルートプレフィックスを無視する場合はチルダ(~)を使用
[Route("~/api/external/check")]
public IHttpActionResult ExternalCheck() { ... }
}
ルート制約(Route Constraints)
URI パラメータに対して、データ型や範囲などの制限をかけることができます。構文は {parameter:constraint} です。
[Route("products/{id:int}")] // 数値のみ
public Product GetByIntId(int id) { ... }
[Route("products/{code:alpha}")] // 英字のみ
public Product GetByCode(string code) { ... }
利用可能な主な制約は以下の通りです。
| 制約名 | 内容 | 例 |
|---|---|---|
int |
32ビット整数 | {id:int} |
guid |
GUID 値 | {id:guid} |
min / max |
数値の最小・最大 | {age:min(18)} |
length |
文字列の長さ | {name:length(5, 20)} |
regex |
正規表現 | {date:regex(^\d{4}-\d{2}-\d{2}$)} |
オプションパラメータとデフォルト値
パラメータ名の末尾に ? を付けるとオプション(省略可能)になります。この場合、メソッドの引数にデフォルト値を設定する必要があります。
[Route("search/{query?}")]
public IEnumerable<Result> Search(string query = "all")
{
return results;
}
または、属性内で直接デフォルト値を指定することも可能です。
[Route("items/{page:int=1}")]
public IEnumerable<Item> GetPagedItems(int page) { ... }
ルートの順序制御(RouteOrder)
複数のルートが同じ URI にマッチする可能性がある場合、RouteOrder プロパティで優先順位を指定できます。値が小さいほど先に評価されます。
[Route("plugins/system", RouteOrder = 1)]
public IHttpActionResult GetSystemPlugin() { ... }
[Route("plugins/{name}", RouteOrder = 2)]
public IHttpActionResult GetPluginByName(string name) { ... }
カスタム制約の作成
標準の制約では不足する場合、IHttpRouteConstraint インターフェースを実装して独自のロジックを作成できます。例えば、正の整数のみを許容する制約は以下のように実装します。
public class PositiveConstraint : IHttpRouteConstraint
{
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (values.TryGetValue(parameterName, out object value) && value != null)
{
if (long.TryParse(value.ToString(), out long longValue))
{
return longValue > 0;
}
}
return false;
}
}
このカスタム制約を DefaultInlineConstraintResolver に登録することで、ルーティング属性内で {id:positive} のように利用可能になります。