ASP.NET Core でのコントローラー アクションへのルーティング
作成者: Ryan Nowak、Kirk Larkin、Rick Anderson
注意
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
ASP.NET Core コントローラーは、ルーティング ミドルウェアを使って、受信した要求の URL を照合し、アクションにマップします。 ルート テンプレートは、
- "
Program.cs
" または属性で起動時に定義されます。 - URL パスとアクションの照合方法が記述されています。
- リンクの URL を生成するために使用されます。 生成されたリンクは通常、応答で返されます。
アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性でルーティングされるようになります。 詳しくは、「混合ルーティング」をご覧ください。
このドキュメントでは、
- MVC とルーティングの間の相互作用について説明します。
- 一般的な MVC アプリでルーティング機能を利用する方法。
- 以下の 2 つについて説明します。
- 通常は、コントローラーとビューで使用される規則ルーティング。
- REST API で使用される属性ルーティング。 REST API のルーティングに主に関心がある場合は、「REST API の属性ルーティング」セクションに進んでください。
- 高度なルーティングについて詳しくは、ルーティングに関する記事を参照してください。
- エンドポイント ルーティングと呼ばれる既定のルーティング システムについて説明します。 互換性を確保するために、以前のバージョンのルーティングでコントローラーを使用することができます。 手順については、2.2-3.0 の移行ガイドを参照してください。
規則ルートの設定
ASP.NET Core MVC テンプレートでは、次のような規則ルーティング コードが生成されます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
MapControllerRoute は、単一ルートを作成するために使用されます。 この単一ルートの名前は default
ルートです。 コントローラーとビューを使用するほとんどのアプリでは、default
ルートと同様のルート テンプレートが使用されます。 REST API では、属性ルーティングを使用する必要があります。
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ルート テンプレート "{controller=Home}/{action=Index}/{id?}"
:
/Products/Details/5
のような URL パスと一致しますパスをトークン化して、ルートの値
{ controller = Products, action = Details, id = 5 }
を抽出します。 アプリにProductsController
という名前のコントローラーとDetails
アクションがある場合、ルート値の抽出が一致します。public class ProductsController : Controller { public IActionResult Details(int id) { return ControllerContext.MyDisplayRouteInfo(id); } }
MyDisplayRouteInfo は Rick.Docs.Samples.RouteInfo NuGet パッケージによって提供され、ルート情報が表示されます。
/Products/Details/5
モデルは、id = 5
の値をバインドして、id
パラメーターを5
に設定します。 詳しくは、モデル バインドに関する記事を参照してください。{controller=Home}
は、Home
を既定のcontroller
として定義します。{action=Index}
は、Index
を既定のaction
として定義します。{id?}
の文字?
は、id
を省略可能として定義します。- 既定および省略可能のルート パラメーターは、URL パスに存在していなくても一致します。 ルート テンプレートの構文について詳しくは、「ルート テンプレート参照」をご覧ください。
URL パス
/
と一致します。ルート値
{ controller = Home, action = Index }
を生成します。
controller
と action
の値に、既定値が使用されます。 URL パスに対応するセグメントがないため、id
は値を生成しません。 HomeController
と Index
アクションが存在する場合のみ、/
は一致します。
public class HomeController : Controller
{
public IActionResult Index() { ... }
}
上のコントローラー定義とルート テンプレートを使うと、HomeController.Index
アクションは次のいずれかの URL パスに対して実行されます。
/Home/Index/17
/Home/Index
/Home
/
URL パス /
では、ルート テンプレートの既定の Home
コントローラーと Index
アクションが使用されます。 URL パス /Home
では、ルート テンプレートの既定の Index
アクションが使用されます。
便利なメソッド MapDefaultControllerRoute:
app.MapDefaultControllerRoute();
次のように置き換えます。
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
重要
ルーティングは、UseRouting と UseEndpoints ミドルウェアを使用して構成します。 コントローラーを使用するには、
- MapControllers を呼び出して、属性でルーティングされたコントローラーをマップします。
- MapControllerRoute または MapAreaControllerRoute を呼び出して、規則的にルーティングされたコントローラーと属性でルーティングされたコントローラーの両方をマップします。
通常は、アプリで UseRouting
または UseEndpoints
を呼び出す必要はありません。 WebApplicationBuilder は、Program.cs
に追加されたミドルウェアを UseRouting
と UseEndpoints
でラップするミドルウェア パイプラインを構成します。 詳細については、「ASP.NET Core のルーティング」を参照してください。
規則ルーティング
規則ルーティングは、コントローラーやビューで使用されます。 次は default
ルートです。
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
これは、"規則ルーティング" の例です。 これを "規則ルーティング" と呼ぶのは、それが URL パスの "規則" を作成するためです。
- 最初のパス セグメント
{controller=Home}
は、コントローラー名にマップします。 - 2 番目のセグメント
{action=Index}
は、アクション名にマップします。 - 3 番目のセグメント
{id?}
は、省略可能なid
に使われます。{id?}
の?
によって、省略可能になります。id
は、モデル エンティティにマップするために使用されます。
この default
ルートを使用すると、URL パスは次のようになります。
/Products/List
は、ProductsController.List
アクションにマップします。/Blog/Article/17
は、BlogController.Article
にマップし、通常id
パラメーターを 17 にバインドします。
このマッピングは、
- コントローラーとアクションの名前にのみ基づきます。
- 名前空間、ソース ファイルの場所、またはメソッドのパラメーターには基づきません。
既定のルートで規則ルーティングを使うと、アクションごとに新しい URL パターンを考える必要なしにアプリを作成できます。 CRUD スタイルのアクションを使用するアプリの場合、コントローラー間で URL の一貫性を保つことは、
- コードを簡略化するのに役立ちます。
- UI の予測可能性を向上させます。
警告
上のコードの id
は、ルート テンプレートによって省略可能として定義されています。 アクションは、URL の一部として指定された省略可能な ID なしで実行できます。 通常、URL から id
を省略すると、次のようになります。
id
はモデル バインドによって0
に設定されます。- データベース照合
id == 0
でエンティティが見つかりません。
属性ルーティングを使うと、ID が必須のアクションと必須ではないアクションをきめ細かく制御できます。 慣例に従って、ドキュメントでは id
などの省略可能なパラメーターが正しい使用法で使われる可能性がある場合はパラメーターを記載してあります。
ほとんどのアプリでは、URL を読みやすくてわかりやすいものにするために、基本的なでわかりやすいルーティング スキームを選択する必要があります。 既定の規則ルート {controller=Home}/{action=Index}/{id?}
:
- 基本的でわかりやすいルーティング スキームをサポートしています。
- UI ベースのアプリの便利な開始点となります。
- 多くの Web UI アプリに必要な唯一のルート テンプレートになります。 大規模な Web UI アプリの場合でも、大抵は区分を使用するもう 1 つのルートがあれば十分です。
MapControllerRoute と MapAreaRoute では、
- それぞれが呼び出された順序に基づいて、それぞれのエンドポイントに順序値が自動的に割り当てられます。
ASP.NET Core でのエンドポイントのルーティングは、
- ルートの概念がありません。
- 拡張性の実行に対して順序は保証されません。すべてのエンドポイントは一度に処理されます。
ログを有効にすると、Route など、組み込みのルーティング実装で要求を照合するしくみを確認できます。
属性ルーティングについては、このドキュメントの後で説明します。
複数の規則ルート
MapControllerRoute と MapAreaControllerRoute の呼び出しをさらに追加することで、複数の規則ルートを構成できます。 これにより、次のように、複数の規則を定義したり、特定のアクションに専用の規則ルートを追加したりすることができます。
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
上の例の blog
が、専用の規則ルートです。 これは、次の理由で専用の規則ルートと呼ばれます。
controller
と action
は、パラメーターとしてルート テンプレート "blog/{*article}"
に表示されないため、
- 既定値
{ controller = "Blog", action = "Article" }
しか指定できません。 - このルートは常にアクション
BlogController.Article
にマップされます。
/Blog
、/Blog/Article
、および /Blog/{any-string}
は、ブログ ルートに一致する唯一の URL パスです。
上記の例の場合:
blog
ルートは最初に追加されるので、default
ルートよりも照合の優先度が高くなります。- URL の一部として記事名を持つのが一般的な Slug スタイル ルーティングの例が示されています。
警告
ASP.NET Core では、ルーティングで以下は行いません。
- "ルート" と呼ばれる概念の定義。
UseRouting
では、ルートの照合がミドルウェア パイプラインに追加されます。UseRouting
ミドルウェアによって、アプリで定義されているエンドポイントのセットが調べられ、要求に基づいて最適な一致が選択されます。 - IRouteConstraint や IActionConstraint のような機能拡張の実行順序に関する保証。
詳細については、ルーティングに関する記事を参照してください。
規則ルーティングの順序
規則ルーティングは、アプリで定義されているアクションとコントローラーの組み合わせにのみ一致します。 これは、規則ルートが重複する場合に簡略化することを目的としています。
MapControllerRoute、MapDefaultControllerRoute、および MapAreaControllerRoute を使用してルートを追加すると、それぞれが呼び出された順序に基づいて、それぞれのエンドポイントに順序値が自動的に割り当てられます。 先に表示されたルートの一致の優先度が高くなります。 規則ルーティングは順序に依存します。 通常、区分のあるルートは区分を持たないルートより具体的なので、区分のあるルートは前の方に配置する必要があります。 {*article}
のようなキャッチオール ルートのパラメーターを持つ専用の規則ルートは、ルートの一致範囲が広くなりすぎて、他のルートと一致させるつもりであった URL まで一致する可能性があります。 意図しないルートまで一致しないようにするため、一致範囲が広いルートを後ろに置きます。
警告
ルーティングでバグが原因で、キャッチオール パラメーターがルートと正しく一致しない可能性があります。 このバグの影響を受けるアプリには、次の特性があります。
- キャッチオール ルート (たとえば、
{**slug}"
) - キャッチオール ルートが、一致すべき要求と一致しません。
- 他のルートを削除すると、キャッチオール ルートが機能し始めます。
このバグが発生するケースの例については、GitHub のバグ 18677 および 16579 を参照してください。
このバグのオプトイン修正は .NET Core 3.1.301 SDK 以降に含まれています。 次のコードにより、このバグを修正する内部スイッチが設定されます。
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
あいまいなアクションの解決
2 つのエンドポイントがルーティングで一致する場合、ルーティングで次のいずれかを実行する必要があります。
- 最適な候補を選択します。
- 例外をスローします。
次に例を示します。
public class Products33Controller : Controller
{
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}
上のコントローラーでは、一致する次の 2 つのアクションが定義されています。
- URL パス
/Products33/Edit/17
- ルート データ
{ controller = Products33, action = Edit, id = 17 }
これは MVC コントローラーの一般的なパターンです。
Edit(int)
には、製品を編集するフォームが表示されます。Edit(int, Product)
は、ポストされたフォームを処理します。
正しいルートを解決するため、
Edit(int, Product)
は、要求が HTTPPOST
の場合に選択されます。Edit(int)
は、HTTP 動詞がそれ以外の場合に選択されます。Edit(int)
は通常、GET
を介して呼び出されます。
HttpPostAttribute ([HttpPost]
) は、要求の HTTP メソッドに基づいて選択できるようルーティングに提供されます。 HttpPostAttribute
は、Edit(int)
よりも Edit(int, Product)
の一致を向上させます。
HttpPostAttribute
のような属性の役割を理解することが重要です。 同様の属性は、他の HTTP 動詞に対して定義されます。 規則ルーティングでは、アクションが表示フォームや送信フォームのワークフローの一部になっている場合、複数のアクションが同じアクション名を使うのはよくあることです。 たとえば、2 つの編集アクションのメソッドに関する説明を参照してください。
ルーティングで最適な候補を選択できない場合、AmbiguousMatchException がスローされ、一致する複数のエンドポイントが一覧に表示されます。
規則ルート名
次の例の文字列 "blog"
と "default"
は、規則ルート名です。
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ルート名は、ルートに論理名を付けます。 名前付きルートは、URL の生成に使用できます。 ルートの順序指定によって URL の生成が複雑になる場合に、名前付きルートを使用すると、URL の作成が大幅に簡略化されます。 ルート名は、アプリケーション全体で一意である必要があります。
ルート名には次が適用されます。
- URL の照合や要求の処理に影響を与えません。
- URL の生成にのみ使用されます。
ルート名の概念は、ルーティングで IEndpointNameMetadata として表されます。 ルート名とエンドポイント名は、
- 置き換え可能な用語です。
- ドキュメントとコードでどちらが使用されるかは、説明されている API によって異なります。
REST API の属性ルーティング
REST API では、属性ルーティングを使用して、HTTP 動詞で操作を表現するリソースのセットとしてアプリの機能をモデル化する必要があります。
属性ルーティングでは、属性のセットを使ってアクションをルート テンプレートに直接マップします。 次のコードは、REST API では一般的であり、次のサンプルで使用されます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
上のコードでは、MapControllers が呼び出され、属性ルーティング コントローラーがマップされます。
次に例を示します。
HomeController
は、既定の規則ルート{controller=Home}/{action=Index}/{id?}
が一致するのと同じように、一連の URL に一致します。
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
HomeController.Index
アクションは、URL パス /
、/Home
、/Home/Index
または /Home/Index/3
のいずれかに対して実行されます。
この例では、属性ルーティングと規則ルーティングでのプログラミングの大きな違いが強調して示されています。 属性ルーティングでは、ルートを指定するために追加の入力が必要です。 既定の規則ルートは、より簡潔にルートを処理します。 ただし、属性ルーティングでは、各アクションに適用するルート テンプレートを正確に制御できます (そして制御する必要があります)。
属性ルーティングでは、トークンの置換が使用されていない限り、コントローラーとアクションの名前はアクションの照合で果たす役割はありません。 次の例は、前の例と同じ URL と一致します。
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
次のコードでは、action
と controller
のトークン置換が使用されます。
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("[controller]/[action]")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
次のコードでは、コントローラーに [Route("[controller]/[action]")]
が適用されます。
[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上のコードの Index
メソッド テンプレートで、ルート テンプレートの先頭に /
または ~/
を追加する必要があります。 /
または ~/
で始まるアクションに適用されるルート テンプレートは、コントローラーに適用されるルート テンプレートと結合されません。
ルート テンプレートの選択については、ルート テンプレートの優先順位に関する説明を参照してください。
ルーティングの予約名
次のキーワードは、コントローラーまたは Razor Pages を使用する場合の予約済みルート パラメーターの名前です。
action
area
controller
handler
page
属性ルーティングでルート パラメーターとして page
を使用すると、一般的なエラーが発生します。 これを実行すると、URL 生成で一貫性のない、混乱を招く動作が発生します。
public class MyDemo2Controller : Controller
{
[Route("/articles/{page}")]
public IActionResult ListArticles(int page)
{
return ControllerContext.MyDisplayRouteInfo(page);
}
}
URL 生成操作で Razor Page またはコントローラーが参照されているかどうかを判断するために、URL 生成では特別なパラメーター名が使用されます。
次のキーワードは、Razor ビューまたは Razor ページのコンテキストで予約されています。
page
using
namespace
inject
section
inherits
model
addTagHelper
removeTagHelper
これらのキーワードは、リンクの生成、モデル バインド パラメーター、またはトップ レベルのプロパティには使用できません。
HTTP 動詞テンプレート
ASP.NET Core には、次の HTTP 動詞テンプレートがあります。
ルート テンプレート
ASP.NET Core には、次のルート テンプレートがあります。
- HTTP 動詞テンプレートのすべては、ルート テンプレートにもなります。
- [Route]
Http 動詞属性を使用する属性ルーティング
次のようなコントローラーがあるとします。
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードでは以下の操作が行われます。
- 各アクションには
[HttpGet]
属性が含まれているので、HTTP GET 要求への一致のみを制限します。 GetProduct
アクションには"{id}"
テンプレートが含まれるため、id
がコントローラーの"api/[controller]"
テンプレートに追加されます。 メソッド テンプレートは"api/[controller]/{id}"
です。 したがって、このアクションは、/api/test2/xyz
、/api/test2/123
、/api/test2/{any string}
などの形式の GET 要求にのみ一致します。[HttpGet("{id}")] // GET /api/test2/xyz public IActionResult GetProduct(string id) { return ControllerContext.MyDisplayRouteInfo(id); }
GetIntProduct
アクションは"int/{id:int}"
テンプレートを組み込みます。 テンプレート:int
の部分で、整数に変換できる文字列にid
ルート値を制限します。/api/test2/int/abc
への Get 要求は、- このアクションと一致しません。
- 404 Not Found エラーを返します。
[HttpGet("int/{id:int}")] // GET /api/test2/int/3 public IActionResult GetIntProduct(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
GetInt2Product
アクションには、テンプレートの{id}
が含まれますが、整数に変換できる値にid
を制限しません。/api/test2/int2/abc
への Get 要求は、- このルートと一致します。
- モデル バインドは整数への
abc
の変換に失敗します。 メソッドのid
パラメーターは整数です。 - モデル バインドが
abc
を整数に変換できなかったため、400 Bad Request を返します。[HttpGet("int2/{id}")] // GET /api/test2/int2/3 public IActionResult GetInt2Product(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
属性ルーティングでは、HttpPostAttribute、HttpPutAttribute、HttpDeleteAttribute などの HttpMethodAttribute 属性を使用できます。 HTTP 動詞属性はすべて、ルート テンプレートを受け入れます。 次の例では、同じルート テンプレートと一致する 2 つのアクションが示されています。
[ApiController]
public class MyProductsController : ControllerBase
{
[HttpGet("/products3")]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpPost("/products3")]
public IActionResult CreateProduct(MyProduct myProduct)
{
return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
}
}
URL パス /products3
を使用すると、
- HTTP 動詞が
GET
の場合にMyProductsController.ListProducts
アクションが実行されます。 - HTTP 動詞が
POST
の場合にMyProductsController.CreateProduct
アクションが実行されます。
REST API を構築する場合、アクションですべての HTTP メソッドが受け入れられるため、アクション メソッドで [Route(...)]
を使用する必要があるのはまれです。 より具体的な HTTP 動詞属性を使って、API がサポートするものを正確に指定することをお勧めします。 REST API のクライアントは、パスおよび HTTP 動詞と特定の論理操作のマップを理解していることを期待されます。
REST API では、属性ルーティングを使用して、HTTP 動詞で操作を表現するリソースのセットとしてアプリの機能をモデル化する必要があります。 つまり、同じ論理リソース上の多くの操作 (たとえば GET や POST) で、同じ URL が使用されます。 属性ルーティングでは、API のパブリック エンドポイント レイアウトを慎重に設計するために必要となるコントロールのレベルが提供されます。
属性ルートは特定のアクションに適用されるため、ルート テンプレート定義の一部として簡単にパラメーターを必須にできます。 次の例では、id
は URL パスの一部として必須です。
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Products2ApiController.GetProduct(int)
アクションは、
/products2/3
のような URL パスで実行されます。- URL パス
/products2
では実行されません。
[Consumes] 属性を使用すると、アクションでサポートされる要求のコンテンツの種類を制限できます。 詳細については、「Consumes 属性を使ってサポートされる要求のコンテンツの種類を定義する」を参照してください。
ルート テンプレートと関連するオプションについて詳しくは、「ルーティング」をご覧ください。
[ApiController]
の詳細については、ApiController 属性に関する記事を参照してください。
ルート名
次のコードでは、Products_List
のルート名を定義しています。
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
ルート名を使うと、特定のルートに基づいて URL を生成できます。 ルート名には次が適用されます。
- ルーティングの URL 照合動作には影響しません。
- URL の生成にのみ使用されます。
ルート名は、アプリケーション全体で一意である必要があります。
上のコードと、id
パラメーターを省略可能 ({id?}
) として定義する規則の既定ルートを比較します。 API を厳密に指定するこの機能には、/products
と /products/5
を異なるアクションに対してディスパッチできるといった利点があります。
属性ルートの組み合わせ
属性ルーティングの反復を少なくするため、コントローラーのルート属性は個々のアクションでのルート属性と結合されます。 コントローラーで定義されているルート テンプレートが、アクションのルート テンプレートの前に付加されます。 ルート属性をコントローラーに配置すると、コントローラーのすべてのアクションが属性ルーティングを使います。
[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
[HttpGet]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
前の例の場合:
- URL パス
/products
はProductsApi.ListProducts
と一致させることができます。 - URL パス
/products/5
はProductsApi.GetProduct(int)
と一致させることができます。
どちらのアクションも、[HttpGet]
属性でマークされているため、HTTP GET
だけと一致します。
/
または ~/
で始まるアクションに適用されるルート テンプレートは、コントローラーに適用されるルート テンプレートと結合されません。 次の例は、既定ルートと同様の URL パスのセットと一致します。
[Route("Home")]
public class HomeController : Controller
{
[Route("")]
[Route("Index")]
[Route("/")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("About")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
次の表では、上のコードの [Route]
属性について説明します。
属性 | [Route("Home")] との組み合わせ |
ルート テンプレートを定義 |
---|---|---|
[Route("")] |
はい | "Home" |
[Route("Index")] |
はい | "Home/Index" |
[Route("/")] |
無効 | "" |
[Route("About")] |
はい | "Home/About" |
属性ルートの順序
ルーティングによってツリーが構築され、すべてのエンドポイントが同時に照合されます。
- ルート エントリは、理想的な順序で配置されているかのように動作します。
- 最も具体的なルートは、より一般的なルートの前に実行される可能性があります。
たとえば、blog/search/{topic}
のような属性ルートは、blog/{*article}
のような属性ルートよりも具体的です。 blog/search/{topic}
ルートの優先順位は、より具体的なので、既定では高くなります。 規則ルーティングを使うときは、開発者が目的の順序でルートを配置する必要があります。
属性ルートでは、Order プロパティを使用して順番を構成できます。 フレームワークで提供されるすべてのルート属性には、Order
が含まれます。 ルートは、Order
プロパティの昇順に従って処理されます。 既定の順序は 0
です。 Order = -1
でルートを設定する場合、順序が設定されていないルートの前に実行されます。 Order = 1
でルートを設定する場合、既定のルート順序の後に実行されます。
Order
には依存しないでください。 アプリの URL 空間で正しくルーティングするために明示的な順序値が必要な場合、クライアントの混乱を招く可能性があります。 一般に、属性ルーティングは URL 照合で正しいルートを選びます。 URL の生成に使われる既定の順序がうまくいかない場合は、通常、オーバーライドとしてルート名を使う方が、Order
プロパティを適用するより簡単です。
次の 2 つのコントローラーを考え、両方で /home
と一致するルートを定義します。
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードで /home
を要求すると、次のような例外がスローされます。
AmbiguousMatchException: The request matched multiple endpoints. Matches:
WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex
ルート属性の 1 つに Order
を追加すると、あいまいさが解決されます。
[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
return ControllerContext.MyDisplayRouteInfo();
}
上のコードでは、/home
は HomeController.Index
エンドポイントを実行します。 MyDemoController.MyIndex
を取得するには、/home/MyIndex
を要求します。 注:
- 上のコードは一例であり、ルーティング設計は不十分です。
Order
プロパティの説明のために使用しました。 Order
プロパティはあいまいさを解決しますが、そのテンプレートを照合できません。[Route("Home")]
テンプレートを削除することをお勧めします。
Razor Pages でのルート順序については、Razor Pages のルートとアプリの規則に関する記事の「ルート順序」を参照してください。
場合によっては、あいまいなルートで HTTP 500 エラーが返されます。 ログを使用して、AmbiguousMatchException
の原因となるエンドポイントを確認します。
ルート テンプレートでのトークンの置換 ([controller]、[action]、[area])
利便性のため、属性ルートではトークンを角かっこ ([
、]
) で囲むことによる "トークンの置換" がサポートされています。 トークン [action]
、[area]
、および [controller]
は、ルートが定義されているアクションのアクション名、区分名、コントローラー名の値に置き換えられます。
[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードでは以下の操作が行われます。
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
/Products0/List
と一致します
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
/Products0/Edit/{id}
と一致します
属性ルート作成の最後のステップで、トークンの置換が発生します。 上の例では、次のコードと同じ動作が実行されます。
public class Products20Controller : Controller
{
[HttpGet("[controller]/[action]")] // Matches '/Products20/List'
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("[controller]/[action]/{id}")] // Matches '/Products20/Edit/{id}'
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
英語以外の言語でこの記事をお読みになっていて、コードのコメントをネイティブ言語でご覧になりたい場合は、この GitHub ディスカッション イシューでお知らせください。
属性ルートを継承と組み合わせることもできます。 これをトークンの置換と組み合わせると強力です。 トークンの置換は、属性ルートで定義されているルート名にも適用されます。
[Route("[controller]/[action]", Name="[controller]_[action]")]
では、アクションごとに一意のルート名が生成されます。
[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}
public class Products11Controller : MyBase2Controller
{
[HttpGet] // /api/products11/list
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
リテラル トークン置換区切り文字 [
または ]
と一致させるためには、その文字を繰り返すことでエスケープします ([[
または ]]
)。
パラメーター トランスフォーマーを使用してトークンの置換をカスタマイズする
トークンの置換は、パラメーター トランスフォーマーを使用してカスタマイズできます。 パラメーター トランスフォーマーは IOutboundParameterTransformer を実装し、パラメーターの値を変換します。 たとえば、SlugifyParameterTransformer
パラメーター トランスフォーマーでは、SubscriptionManagement
のルート値が subscription-management
に変更されます。
using System.Text.RegularExpressions;
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object? value)
{
if (value == null) { return null; }
return Regex.Replace(value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
RouteTokenTransformerConvention は、次のようなアプリケーション モデルの規則です。
- パラメーター トランスフォーマーをアプリケーションのすべての属性ルートに適用します。
- 置き換えられる属性ルートのトークン値をカスタマイズします。
public class SubscriptionManagementController : Controller
{
[HttpGet("[controller]/[action]")]
public IActionResult ListAll()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上の /subscription-management/list-all
メソッドでは ListAll
と一致します。
RouteTokenTransformerConvention
は、省略可能として登録されます。
using Microsoft.AspNetCore.Mvc.ApplicationModels;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Slug の定義については、 Slug の MDN Web ドキュメントを参照してください。
警告
System.Text.RegularExpressions を使用して信頼できない入力を処理するときは、タイムアウトを渡します。 悪意のあるユーザーが RegularExpressions
に入力を提供して、サービス拒否攻撃を行う可能性があります。 RegularExpressions
を使用する ASP.NET Core フレームワーク API は、タイムアウトを渡します。
複数の属性ルート
属性ルーティングでは、同じアクションに到達する複数のルートの定義がサポートされています。 これの最も一般的な使用方法は、次の例で示すように、"既定の規則ルート" の動作を模倣することです。
[Route("[controller]")]
public class Products13Controller : Controller
{
[Route("")] // Matches 'Products13'
[Route("Index")] // Matches 'Products13/Index'
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
コントローラーに複数のルート属性を配置すると、それぞれが、アクション メソッドの各ルート属性と結合します。
[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
[HttpPost("Buy")] // Matches 'Products6/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products6/Checkout' and 'Store/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
すべての HTTP 動詞ルート制約は、IActionConstraint
を実装します。
IActionConstraint を実装する複数のルート属性が 1 つのアクションに配置されている場合:
- 各アクション制約は、コントローラーに適用されたルート テンプレートと組み合わされます。
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
[HttpPut("Buy")] // Matches PUT 'api/Products7/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products7/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
アクションで複数のルートを使用すると便利で強力な場合があります。アプリの URL 空間の基本的な状態を維持し、明確に定義しておくことをお勧めします。 アクションで複数のルートを使うのは、必要な場合 (既存のクライアントをサポートする、など) だけにしてください。
属性ルートの省略可能なパラメーター、既定値、制約を指定する
属性ルートでは、省略可能なパラメーター、既定値、および制約の指定に関して、規則ルートと同じインライン構文がサポートされています。
public class Products14Controller : Controller
{
[HttpPost("product14/{id:int}")]
public IActionResult ShowProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードでは、[HttpPost("product14/{id:int}")]
はルート制約を適用します。 Products14Controller.ShowProduct
アクションは、/product14/3
のような URL パスによってのみ照合されます。 ルート テンプレートの部分 {id:int}
は、そのセグメントを整数のみに制限します。
ルート テンプレートの構文について詳しくは、「ルート テンプレート参照」をご覧ください。
IRouteTemplateProvider を使用したカスタム ルート属性
すべてのルート属性は IRouteTemplateProvider を実装します。 ASP.NET Core ランタイムは、
- アプリの起動時に、コントローラー クラスとアクション メソッドの属性を検索します。
IRouteTemplateProvider
を実装する属性を使用して、ルートの初期セットを構築します。
IRouteTemplateProvider
を実装して、カスタム ルート属性を定義します。 各 IRouteTemplateProvider
では、カスタム ルート テンプレート、順序、名前を使って 1 つのルートを定義できます。
public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
public string Template => "api/[controller]";
public int? Order => 2;
public string Name { get; set; } = string.Empty;
}
[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上の Get
メソッドでは Order = 2, Template = api/MyTestApi
が返されます。
アプリケーション モデルを使用した属性ルートのカスタマイズ
アプリケーション モデルは、
Program.cs
で起動時に作成されるオブジェクト モデルです。- アプリでアクションをルーティングしたり、実行したりするために ASP.NET Core によって使用されるすべてのメタデータが含まれます。
アプリケーション モデルには、ルート属性から収集されるすべてのデータが含まれます。 ルート属性からのデータは、IRouteTemplateProvider
実装によって提供されます。 表記規則:
- アプリケーション モデルを変更して、ルーティングの動作をカスタマイズするために、記述できます。
- アプリの起動時に読み込まれます。
このセクションでは、アプリケーション モデルを使ってルーティングをカスタマイズする基本的な例を示します。 次のコードでは、ルートがプロジェクトのフォルダー構造とほぼ同じになるようにします。
public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
private readonly string _baseNamespace;
public NamespaceRoutingConvention(string baseNamespace)
{
_baseNamespace = baseNamespace;
}
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
var namespc = controller.ControllerType.Namespace;
if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]/[action]/{id?}");
foreach (var selector in controller.Selectors)
{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}
次のコードでは、属性でルーティングされているコントローラーに namespace
規則が適用されないようにします。
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
たとえば、次のコントローラーでは NamespaceRoutingConvention
を使用しません。
[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
// /managers/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
return Content($"Index- template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
NamespaceRoutingConvention.Apply
メソッド:
- コントローラーが属性でルーティングされている場合は、何も実行しません。
namespace
に基づいてコントローラー テンプレートを設定します。ベースnamespace
は削除されます。
NamespaceRoutingConvention
は Program.cs
で適用できます。
using My.Application.Controllers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});
var app = builder.Build();
たとえば、次のようなコントローラーがあるとします。
using Microsoft.AspNetCore.Mvc;
namespace My.Application.Admin.Controllers
{
public class UsersController : Controller
{
// GET /admin/controllers/users/index
public IActionResult Index()
{
var fullname = typeof(UsersController).FullName;
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var path = Request.Path.Value;
return Content($"Path: {path} fullname: {fullname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"Path: {path} ID:{id}");
}
}
}
上のコードでは以下の操作が行われます。
- ベース
namespace
はMy.Application
です。 - 上のコントローラーの完全な名前は
My.Application.Admin.Controllers.UsersController
です。 NamespaceRoutingConvention
によって、コントローラー テンプレートがAdmin/Controllers/Users/[action]/{id?
に設定されます。
NamespaceRoutingConvention
は、コントローラーの属性としても適用できます。
[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
// /admin/controllers/test/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var actionname = ControllerContext.ActionDescriptor.ActionName;
return Content($"Action- {actionname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
混合ルーティング: 属性ルーティングと規則ルーティング
ASP.NET Core アプリケーションでは、規則ルーティングと属性ルーティングを併用できます。 一般に、ブラウザーに HTML ページを提供するコントローラーには規則ルートを使い、REST API を提供するコントローラーには属性ルーティングを使います。
アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性ルーティングされるようになります。 属性ルートが定義されているアクションには規則ルートでは到達できず、規則ルートが定義されているアクションには属性ルートでは到達できません。 コントローラーの任意のルート属性により、コントローラー属性のすべてのアクションがルーティングされます。
属性ルーティングと規則ルーティングで、同じルーティング エンジンが使用されます。
特殊文字を含むルーティング
特殊文字を含むルーティングは、予期しない結果になる可能性があります。 たとえば、次のアクション メソッドを含むコントローラーについて考えます。
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null || todoItem.Name == null)
{
return NotFound();
}
return todoItem.Name;
}
string id
に次のエンコードされた値が含まれる場合、予期しない結果が発生する可能性があります。
ASCII | Encoded |
---|---|
/ |
%2F |
|
+ |
ルート パラメーターは常に URL でデコードされるとは限りません。 この問題は今後対処される可能性があります。 詳しくは、こちらの GitHub イシューに関するページをご覧ください。
URL 生成とアンビエント値
アプリでは、ルーティングの URL 生成機能を使って、アクションへの URL リンクを生成できます。 URL を生成すると URL をハードコーディングする必要がなくなり、コードの堅牢性と保守性が向上します。 このセクションでは、MVC によって提供される URL 生成機能について説明します。URL 生成のしくみに関する基本だけを取り上げます。 URL の生成について詳しくは、「ルーティング」をご覧ください。
IUrlHelper インターフェイスは、MVC と URL 生成のルーティングの間にあるインフラストラクチャの基盤となる要素です。 IUrlHelper
のインスタンスは、コントローラー、ビュー、およびビュー コンポーネントの Url
プロパティを使って使用できます。
次の例の IUrlHelper
インターフェイスは、別のアクションへの URL を生成するために Controller.Url
プロパティを介して使われています。
public class UrlGenerationController : Controller
{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
アプリが既定の規則ルートを使っている場合、url
変数の値は URL パス文字列 /UrlGeneration/Destination
になります。 この URL パスは、次の組み合わせで、ルーティングによって作成されます。
- アンビエント値と呼ばれる、現在の要求のルート値。
Url.Action
に渡され、それらの値をルート テンプレートに置き換える値。
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}
result: /UrlGeneration/Destination
ルート テンプレートの各ルート パラメーターの値は、一致する名前と値およびアンビエント値によって置換されます。 値を持たないルート パラメーターは、
- 既定値がある場合は、それを使用できます。
- 省略可能な場合はスキップできます (たとえば、ルート テンプレート
{controller}/{action}/{id?}
のid
)。
必須のルート パラメーターに対応する値がない場合、URL の生成は失敗します。 ルートの URL 生成が失敗した場合、すべてのルートが試されるまで、または一致が見つかるまで、次のルートが試されます。
上の Url.Action
の例では、規則ルーティングを想定しています。 URL 生成は属性ルーティングでも同じように動作しますが、概念は異なります。 規則ルーティングでは、
- ルート値は、テンプレートを展開するために使用されます。
controller
とaction
のルート値は通常、そのテンプレートに表示されます。 これは、ルーティングによって一致した URL が規則に準拠しているために機能します。
次の例では、属性ルーティングが使用されています。
public class UrlGenerationAttrController : Controller
{
[HttpGet("custom")]
public IActionResult Source()
{
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上のコードの Source
アクションによって、custom/url/to/destination
が生成されます。
LinkGenerator は、IUrlHelper
の代わりに ASP.NET Core 3.0 で追加されました。 LinkGenerator
は同様のより柔軟な機能を提供します。 IUrlHelper
の各メソッドには、LinkGenerator
に対応するメソッド ファミリが存在します。
アクション名による URL の生成
Url.Action、LinkGenerator.GetPathByAction、および関連するすべてのオーバーロードは、コントローラー名とアクション名を指定して、ターゲット エンドポイントを生成するように設計されています。
Url.Action
を使用する場合、controller
と action
の現在のルート値は、ランタイムによって提供されます。
controller
およびaction
の値は、アンビエント値と値の両方の一部です。Url.Action
メソッドは、常にaction
とcontroller
の現在の値を使い、現在のアクションにルーティングする URL パスを生成します。
ルーティングの際に、URL の生成時に指定されなかった情報を、アンビエント値を使って埋めようとします。 アンビエント値 { a = Alice, b = Bob, c = Carol, d = David }
を使用する {a}/{b}/{c}/{d}
のようなルートを考えてみましょう。
- ルーティングには、URL を生成するための十分な情報が含まれており、追加の値は必要ありません。
- すべてのルート パラメーターに値があるため、ルーティングには十分な情報があります。
値 { d = Donovan }
が追加された場合、
- 値
{ d = David }
は無視されます。 - 生成された URL パスは
Alice/Bob/Carol/Donovan
です。
警告: URL パスは階層的です。 上の例で、値 { c = Cheryl }
が追加されている場合、
- 値
{ c = Carol, d = David }
は両方とも無視されます。 d
の値は存在せず、URL の生成は失敗します。- URL を生成するには、
c
とd
に必要な値を指定する必要があります。
既定のルート {controller}/{action}/{id?}
では、この問題が発生する可能性があります。 Url.Action
では常に controller
と action
の値が明示的に指定されているため、実際にはこの問題はめったに起こりません。
Url.Action のいくつかのオーバーロードでは、ルート値のオブジェクトを受け取って、controller
と action
以外のルート パラメーターの値を提供します。 ルート値オブジェクトは、id
と共によく使用されます。 たとえば、「 Url.Action("Buy", "Products", new { id = 17 })
」のように入力します。 ルート値オブジェクトは、
- 慣例により、通常は匿名型のオブジェクトです。
IDictionary<>
または POCO になります。
ルート パラメーターと一致しないすべての追加ルート値は、クエリ文字列に格納されます。
public IActionResult Index()
{
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url!);
}
上のコードによって /Products/Buy/17?color=red
が生成されます。
次のコードでは、絶対 URL が生成されます。
public IActionResult Index2()
{
var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
// Returns https://localhost:5001/Products/Buy/17
return Content(url!);
}
絶対 URL を作成するには、次のいずれかを使用します。
protocol
を受け入れるオーバーロード。 たとえば、上のコードを使用します。- 既定で絶対 URI を生成する LinkGenerator.GetUriByAction。
ルートによる URL の生成
上のコードでは、コントローラーとアクション名を渡すことによる URL の生成を示しました。 IUrlHelper
では、Url.RouteUrl ファミリ メソッドも提供されています。 これらのメソッドは、Url.Action と似ていますが、action
と controller
の現在の値をルート値にコピーしません。 Url.RouteUrl
の最も一般的な使用方法は、次のとおりです。
- URL を生成するルート名を指定します。
- 通常、コントローラーまたはアクション名は指定しません。
public class UrlGeneration2Controller : Controller
{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
次の Razor ファイルでは、Destination_Route
への HTML リンクが生成されます。
<h1>Test Links</h1>
<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>
HTML および Razor での URL の生成
IHtmlHelper は HtmlHelper メソッドの Html.BeginForm と Html.ActionLink を提供し、それぞれ <form>
と <a>
要素を生成します。 これらのメソッドは Url.Action メソッドを使って URL を生成するので、同じような引数を受け取ります。 HtmlHelper
の Url.RouteUrl
コンパニオンは、同様の機能を持つ Html.BeginRouteForm
と Html.RouteLink
です。
TagHelper は、form
TagHelper と <a>
TagHelper を使って URL を生成します。 これらはどちらも、実装に IUrlHelper
を使います。 詳しくは、フォームのタグ ヘルパーに関するページをご覧ください。
ビューの内部では、Url
プロパティを使って任意のアドホック URL 生成に IUrlHelper
を使うことができます (上の記事では説明されていません)。
アクションの結果での URL の生成
上の例では、コントローラーで IUrlHelper
を使用する方法を示しました。 コントローラーでの最も一般的な使用方法は、アクションの結果の一部として URL を生成する方法です。
基底クラス ControllerBase および Controller では、別のアクションを参照するアクション結果用の便利なメソッドが提供されています。 一般的な使用方法の 1 つは、ユーザー入力を受け付けた後でリダイレクトする方法です。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
if (ModelState.IsValid)
{
// Update DB with new details.
ViewData["Message"] = $"Successful edit of customer {id}";
return RedirectToAction("Index");
}
return View(customer);
}
RedirectToAction や CreatedAtAction のようなアクション結果ファクトリ メソッドは、IUrlHelper
のメソッドに似たパターンに従います。
専用規則ルートの特殊なケース
規則ルーティングでは、専用規則ルートと呼ばれる特殊なルート定義を使うことができます。 次の例の blog
という名前のルートが専用規則ルートです。
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
上のルート定義を使うと、Url.Action("Index", "Home")
は default
ルートで URL パス /
を生成します。なぜでしょうか。 ルート値 { controller = Home, action = Index }
は blog
を使って URL を生成するのに十分であり、結果は /blog?action=Index&controller=Home
になるように思われます。
専用規則ルートは、URL の生成でルートの一致範囲が広くなりすぎるのを防ぐために対応するルート パラメーターを持たない既定値の特別な動作に依存します。 この場合、既定値は { controller = Blog, action = Article }
であり、controller
と action
はどちらもルート パラメーターとして表示されません。 ルーティングが URL 生成を実行するとき、指定された値は既定値と一致する必要があります。 値 { controller = Home, action = Index }
は { controller = Blog, action = Article }
と一致しないため、blog
を使った URL の生成は失敗します。 そして、ルーティングはフォールバックして default
を試み、これは成功します。
Areas
区分は、関連する機能を個別のグループとしてまとめるために使用される MVC の機能です。
- コントローラー アクションのルーティング名前空間。
- ビュー用のフォルダー構造。
区分を使うと、アプリは同じ名前の複数のコントローラーを持つことができます (ただし、区分が異なる場合)。 区分を使い、別のルート パラメーター area
を controller
および action
に追加することで、ルーティングのための階層を作成します。 このセクションでは、ルーティングと区分がどのように相互作用するかについて説明します。 区分をビューで使用する方法の詳細については、区分に関する記事を参照してください。
次の例では、既定の規則ルートと、Blog
という名前の area
に対する area
ルートを使うように、MVC を構成しています。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapAreaControllerRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
app.Run();
上のコードでは、"blog_route"
を作成するために MapAreaControllerRoute が呼び出されています。 2 番目のパラメーター "Blog"
は区分名です。
/Manage/Users/AddUser
のような URL パスを照合すると、"blog_route"
ルートではルート値 { area = Blog, controller = Users, action = AddUser }
が生成されます。 area
ルート値は、area
の既定値によって生成されます。 MapAreaControllerRoute
によって作成されるルートは、次と等しくなります。
app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
MapAreaControllerRoute
は、指定された区分名 (この場合はBlog
) を使い、既定値と、area
に対する制約の両方からルートを作成します。 既定値はルートが常に { area = Blog, ... }
を生成することを保証し、制約では URL を生成するために値 { area = Blog, ... }
が必要です。
規則ルーティングは順序に依存します。 通常、区分のあるルートは区分を持たないルートより具体的なので、区分のあるルートは前の方に配置する必要があります。
上の例を使うと、ルート値 { area = Blog, controller = Users, action = AddUser }
は次のアクションと一致します。
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
[Area] 属性は、コントローラーが区分の一部であることを示します。 このコントローラーは、この Blog
区分にあります。 [Area]
属性を持たないコントローラーはどの区分のメンバーでもなく、area
ルート値がルーティングによって提供されたときは一致しません。 次の例では、リストの最初のコントローラーだけがルート値 { area = Blog, controller = Users, action = AddUser }
と一致できます。
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
// GET /zebra/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
// GET /users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
各コントローラーの名前空間は、完全を期すためにここに示されています。 上のコントローラーで同じ名前空間が使用されている場合は、コンパイラ エラーが発生します。 クラスの名前空間は、MVC のルーティングに対して影響を与えません。
最初の 2 つのコントローラーは区分のメンバーであり、それぞれの区分名が area
ルート値によって提供された場合にのみ一致します。 3 番目のコントローラーはどの区分のメンバーでもなく、ルーティングによって area
の値が提供されない場合にのみ一致します。
"値なし" との一致では、area
の値がないことは、area
の値が null または空の文字列であることと同じです。
区分の内部でアクションを実行するとき、area
のルート値は、URL の生成に使うルーティングのアンビエント値として利用できます。 つまり、既定では、次の例で示すように、区分は URL の生成に対する "付箋" として機能します。
app.MapAreaControllerRoute(name: "duck_route",
areaName: "Duck",
pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
// GET /Manage/users/GenerateURLInArea
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area.
var url = Url.Action("Index", "Home");
// Returns /Manage/Home/Index
return Content(url);
}
// GET /Manage/users/GenerateURLOutsideOfArea
public IActionResult GenerateURLOutsideOfArea()
{
// Uses the empty value for area.
var url = Url.Action("Index", "Home", new { area = "" });
// Returns /Manage
return Content(url);
}
}
}
次のコードでは /Zebra/Users/AddUser
への URL が生成されます。
public class HomeController : Controller
{
public IActionResult About()
{
var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
return Content($"URL: {url}");
}
アクション定義
NonAction 属性が設定されているものを除き、コントローラーのパブリック メソッドはアクションです。
サンプル コード
- MyDisplayRouteInfo は Rick.Docs.Samples.RouteInfo NuGet パッケージによって提供され、ルート情報が表示されます。
- サンプル コードを表示またはダウンロードします (ダウンロード方法)。
デバッグ診断
詳細なルーティング診断出力を行うには、Logging:LogLevel:Microsoft
を Debug
に設定してください。 開発環境では、appsettings.Development.json
でログ レベルを次のように設定します。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
ASP.NET Core コントローラーは、ルーティング ミドルウェアを使って、受信した要求の URL を照合し、アクションにマップします。 ルート テンプレートは、
- スタートアップ コードまたは属性で定義されています。
- URL パスとアクションの照合方法が記述されています。
- リンクの URL を生成するために使用されます。 生成されたリンクは通常、応答で返されます。
アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性でルーティングされるようになります。 詳しくは、「混合ルーティング」をご覧ください。
このドキュメントでは、
- MVC とルーティングの間の相互作用について説明します。
- 一般的な MVC アプリでルーティング機能を利用する方法。
- 以下の 2 つについて説明します。
- 通常は、コントローラーとビューで使用される規則ルーティング。
- REST API で使用される属性ルーティング。 REST API のルーティングに主に関心がある場合は、「REST API の属性ルーティング」セクションに進んでください。
- 高度なルーティングについて詳しくは、ルーティングに関する記事を参照してください。
- エンドポイント ルーティングと呼ばれる、ASP.NET Core 3.0 で追加された既定のルーティング システムを参照します。 互換性を確保するために、以前のバージョンのルーティングでコントローラーを使用することができます。 手順については、2.2-3.0 の移行ガイドを参照してください。 レガシ ルーティング システムについては、このドキュメントの 2.2 バージョンを参照してください。
規則ルートの設定
通常、規則ルーティングを使用する場合は、Startup.Configure
には次のようなコードが含まれます。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
UseEndpoints への呼び出しの内部で、単一ルートを作成するために MapControllerRoute が使用されます。 この単一ルートの名前は default
ルートです。 コントローラーとビューを使用するほとんどのアプリでは、default
ルートと同様のルート テンプレートが使用されます。 REST API では、属性ルーティングを使用する必要があります。
ルート テンプレート "{controller=Home}/{action=Index}/{id?}"
:
/Products/Details/5
のような URL パスと一致しますパスをトークン化して、ルートの値
{ controller = Products, action = Details, id = 5 }
を抽出します。 アプリにProductsController
という名前のコントローラーとDetails
アクションがある場合、ルート値の抽出が一致します。public class ProductsController : Controller { public IActionResult Details(int id) { return ControllerContext.MyDisplayRouteInfo(id); } }
MyDisplayRouteInfo は Rick.Docs.Samples.RouteInfo NuGet パッケージによって提供され、ルート情報が表示されます。
/Products/Details/5
モデルは、id = 5
の値をバインドして、id
パラメーターを5
に設定します。 詳しくは、モデル バインドに関する記事を参照してください。{controller=Home}
は、Home
を既定のcontroller
として定義します。{action=Index}
は、Index
を既定のaction
として定義します。{id?}
の文字?
は、id
を省略可能として定義します。既定および省略可能のルート パラメーターは、URL パスに存在していなくても一致します。 ルート テンプレートの構文について詳しくは、「ルート テンプレート参照」をご覧ください。
URL パス
/
と一致します。ルート値
{ controller = Home, action = Index }
を生成します。
controller
と action
の値に、既定値が使用されます。 URL パスに対応するセグメントがないため、id
は値を生成しません。 HomeController
と Index
アクションが存在する場合のみ、/
は一致します。
public class HomeController : Controller
{
public IActionResult Index() { ... }
}
上のコントローラー定義とルート テンプレートを使うと、HomeController.Index
アクションは次のいずれかの URL パスに対して実行されます。
/Home/Index/17
/Home/Index
/Home
/
URL パス /
では、ルート テンプレートの既定の Home
コントローラーと Index
アクションが使用されます。 URL パス /Home
では、ルート テンプレートの既定の Index
アクションが使用されます。
便利なメソッド MapDefaultControllerRoute:
endpoints.MapDefaultControllerRoute();
次のように置き換えます。
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
重要
ルーティングは、UseRouting、MapControllerRoute
、および MapAreaControllerRoute
ミドルウェアを使用して構成します。 コントローラーを使用するには、
UseEndpoints
の内部で MapControllers を呼び出して、属性でルーティングされたコントローラーをマップします。- MapControllerRoute または MapAreaControllerRoute を呼び出して、規則的にルーティングされたコントローラーと属性でルーティングされたコントローラーの両方をマップします。
規則ルーティング
規則ルーティングは、コントローラーやビューで使用されます。 次は default
ルートです。
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
これは、"規則ルーティング" の例です。 これを "規則ルーティング" と呼ぶのは、それが URL パスの "規則" を作成するためです。
- 最初のパス セグメント
{controller=Home}
は、コントローラー名にマップします。 - 2 番目のセグメント
{action=Index}
は、アクション名にマップします。 - 3 番目のセグメント
{id?}
は、省略可能なid
に使われます。{id?}
の?
によって、省略可能になります。id
は、モデル エンティティにマップするために使用されます。
この default
ルートを使用すると、URL パスは次のようになります。
/Products/List
は、ProductsController.List
アクションにマップします。/Blog/Article/17
は、BlogController.Article
にマップし、通常id
パラメーターを 17 にバインドします。
このマッピングは、
- コントローラーとアクションの名前にのみ基づきます。
- 名前空間、ソース ファイルの場所、またはメソッドのパラメーターには基づきません。
既定のルートで規則ルーティングを使うと、アクションごとに新しい URL パターンを考える必要なしにアプリを作成できます。 CRUD スタイルのアクションを使用するアプリの場合、コントローラー間で URL の一貫性を保つことは、
- コードを簡略化するのに役立ちます。
- UI の予測可能性を向上させます。
警告
上のコードの id
は、ルート テンプレートによって省略可能として定義されています。 アクションは、URL の一部として指定された省略可能な ID なしで実行できます。 通常、URL から id
を省略すると、次のようになります。
id
はモデル バインドによって0
に設定されます。- データベース照合
id == 0
でエンティティが見つかりません。
属性ルーティングを使うと、ID が必須のアクションと必須ではないアクションをきめ細かく制御できます。 慣例に従って、ドキュメントでは id
などの省略可能なパラメーターが正しい使用法で使われる可能性がある場合はパラメーターを記載してあります。
ほとんどのアプリでは、URL を読みやすくてわかりやすいものにするために、基本的なでわかりやすいルーティング スキームを選択する必要があります。 既定の規則ルート {controller=Home}/{action=Index}/{id?}
:
- 基本的でわかりやすいルーティング スキームをサポートしています。
- UI ベースのアプリの便利な開始点となります。
- 多くの Web UI アプリに必要な唯一のルート テンプレートになります。 大規模な Web UI アプリの場合でも、大抵は区分を使用するもう 1 つのルートがあれば十分です。
MapControllerRoute と MapAreaRoute では、
- それぞれが呼び出された順序に基づいて、それぞれのエンドポイントに順序値が自動的に割り当てられます。
ASP.NET Core 3.0 以降でのエンドポイントのルーティング:
- ルートの概念がありません。
- 拡張性の実行に対して順序は保証されません。すべてのエンドポイントは一度に処理されます。
ログを有効にすると、Route など、組み込みのルーティング実装で要求を照合するしくみを確認できます。
属性ルーティングについては、このドキュメントの後で説明します。
複数の規則ルート
MapControllerRoute と MapAreaControllerRoute の呼び出しをさらに追加することで、UseEndpoints
の内部に複数の規則ルートを追加できます。 これにより、次のように、複数の規則を定義したり、特定のアクションに専用の規則ルートを追加したりすることができます。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
上の例の blog
が、専用の規則ルートです。 これは、次の理由で専用の規則ルートと呼ばれます。
controller
と action
は、パラメーターとしてルート テンプレート "blog/{*article}"
に表示されないため、
- 既定値
{ controller = "Blog", action = "Article" }
しか指定できません。 - このルートは常にアクション
BlogController.Article
にマップされます。
/Blog
、/Blog/Article
、および /Blog/{any-string}
は、ブログ ルートに一致する唯一の URL パスです。
上記の例の場合:
blog
ルートは最初に追加されるので、default
ルートよりも照合の優先度が高くなります。- URL の一部として記事名を持つのが一般的な Slug スタイル ルーティングの例が示されています。
警告
3.0 ASP.NET Core 以降では、ルーティングは次を行いません。
- "ルート" と呼ばれる概念の定義。
UseRouting
では、ルートの照合がミドルウェア パイプラインに追加されます。UseRouting
ミドルウェアによって、アプリで定義されているエンドポイントのセットが調べられ、要求に基づいて最適な一致が選択されます。 - IRouteConstraint や IActionConstraint のような機能拡張の実行順序に関する保証。
詳細については、ルーティングに関する記事を参照してください。
規則ルーティングの順序
規則ルーティングは、アプリで定義されているアクションとコントローラーの組み合わせにのみ一致します。 これは、規則ルートが重複する場合に簡略化することを目的としています。
MapControllerRoute、MapDefaultControllerRoute、および MapAreaControllerRoute を使用してルートを追加すると、それぞれが呼び出された順序に基づいて、それぞれのエンドポイントに順序値が自動的に割り当てられます。 先に表示されたルートの一致の優先度が高くなります。 規則ルーティングは順序に依存します。 通常、区分のあるルートは区分を持たないルートより具体的なので、区分のあるルートは前の方に配置する必要があります。 {*article}
のようなキャッチオール ルートのパラメーターを持つ専用の規則ルートは、ルートの一致範囲が広くなりすぎて、他のルートと一致させるつもりであった URL まで一致する可能性があります。 意図しないルートまで一致しないようにするため、一致範囲が広いルートを後ろに置きます。
警告
ルーティングでバグが原因で、キャッチオール パラメーターがルートと正しく一致しない可能性があります。 このバグの影響を受けるアプリには、次の特性があります。
- キャッチオール ルート (たとえば、
{**slug}"
) - キャッチオール ルートが、一致すべき要求と一致しません。
- 他のルートを削除すると、キャッチオール ルートが機能し始めます。
このバグが発生するケースの例については、GitHub のバグ 18677 および 16579 を参照してください。
このバグのオプトイン修正は .NET Core 3.1.301 SDK 以降に含まれています。 次のコードにより、このバグを修正する内部スイッチが設定されます。
public static void Main(string[] args)
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.
あいまいなアクションの解決
2 つのエンドポイントがルーティングで一致する場合、ルーティングで次のいずれかを実行する必要があります。
- 最適な候補を選択します。
- 例外をスローします。
次に例を示します。
public class Products33Controller : Controller
{
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}
}
上のコントローラーでは、一致する次の 2 つのアクションが定義されています。
- URL パス
/Products33/Edit/17
- ルート データ
{ controller = Products33, action = Edit, id = 17 }
これは MVC コントローラーの一般的なパターンです。
Edit(int)
には、製品を編集するフォームが表示されます。Edit(int, Product)
は、ポストされたフォームを処理します。
正しいルートを解決するため、
Edit(int, Product)
は、要求が HTTPPOST
の場合に選択されます。Edit(int)
は、HTTP 動詞がそれ以外の場合に選択されます。Edit(int)
は通常、GET
を介して呼び出されます。
HttpPostAttribute ([HttpPost]
) は、要求の HTTP メソッドに基づいて選択できるようルーティングに提供されます。 HttpPostAttribute
は、Edit(int)
よりも Edit(int, Product)
の一致を向上させます。
HttpPostAttribute
のような属性の役割を理解することが重要です。 同様の属性は、他の HTTP 動詞に対して定義されます。 規則ルーティングでは、アクションが表示フォームや送信フォームのワークフローの一部になっている場合、複数のアクションが同じアクション名を使うのはよくあることです。 たとえば、2 つの編集アクションのメソッドに関する説明を参照してください。
ルーティングで最適な候補を選択できない場合、AmbiguousMatchException がスローされ、一致する複数のエンドポイントが一覧に表示されます。
規則ルート名
次の例の文字列 "blog"
と "default"
は、規則ルート名です。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
ルート名は、ルートに論理名を付けます。 名前付きルートは、URL の生成に使用できます。 ルートの順序指定によって URL の生成が複雑になる場合に、名前付きルートを使用すると、URL の作成が大幅に簡略化されます。 ルート名は、アプリケーション全体で一意である必要があります。
ルート名には次が適用されます。
- URL の照合や要求の処理に影響を与えません。
- URL の生成にのみ使用されます。
ルート名の概念は、ルーティングで IEndpointNameMetadata として表されます。 ルート名とエンドポイント名は、
- 置き換え可能な用語です。
- ドキュメントとコードでどちらが使用されるかは、説明されている API によって異なります。
REST API の属性ルーティング
REST API では、属性ルーティングを使用して、HTTP 動詞で操作を表現するリソースのセットとしてアプリの機能をモデル化する必要があります。
属性ルーティングでは、属性のセットを使ってアクションをルート テンプレートに直接マップします。 次の StartUp.Configure
コードは、REST API では一般的であり、次のサンプルで使用されます。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
上のコードでは、UseEndpoints
内で MapControllers が呼び出され、属性ルーティング コントローラーがマップされます。
次に例を示します。
HomeController
は、既定の規則ルート{controller=Home}/{action=Index}/{id?}
が一致するのと同じように、一連の URL に一致します。
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
HomeController.Index
アクションは、URL パス /
、/Home
、/Home/Index
または /Home/Index/3
のいずれかに対して実行されます。
この例では、属性ルーティングと規則ルーティングでのプログラミングの大きな違いが強調して示されています。 属性ルーティングでは、ルートを指定するために追加の入力が必要です。 既定の規則ルートは、より簡潔にルートを処理します。 ただし、属性ルーティングでは、各アクションに適用するルート テンプレートを正確に制御できます (そして制御する必要があります)。
属性ルーティングでは、トークンの置換が使用されていない限り、コントローラーとアクションの名前はアクションの照合で果たす役割はありません。 次の例は、前の例と同じ URL と一致します。
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
次のコードでは、action
と controller
のトークン置換が使用されます。
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("[controller]/[action]")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
次のコードでは、コントローラーに [Route("[controller]/[action]")]
が適用されます。
[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上のコードの Index
メソッド テンプレートで、ルート テンプレートの先頭に /
または ~/
を追加する必要があります。 /
または ~/
で始まるアクションに適用されるルート テンプレートは、コントローラーに適用されるルート テンプレートと結合されません。
ルート テンプレートの選択については、ルート テンプレートの優先順位に関する説明を参照してください。
ルーティングの予約名
次のキーワードは、コントローラーまたは Razor Pages を使用する場合の予約済みルート パラメーターの名前です。
action
area
controller
handler
page
属性ルーティングでルート パラメーターとして page
を使用すると、一般的なエラーが発生します。 これを実行すると、URL 生成で一貫性のない、混乱を招く動作が発生します。
public class MyDemo2Controller : Controller
{
[Route("/articles/{page}")]
public IActionResult ListArticles(int page)
{
return ControllerContext.MyDisplayRouteInfo(page);
}
}
URL 生成操作で Razor Page またはコントローラーが参照されているかどうかを判断するために、URL 生成では特別なパラメーター名が使用されます。
次のキーワードは、Razor ビューまたは Razor ページのコンテキストで予約されています。
page
using
namespace
inject
section
inherits
model
addTagHelper
removeTagHelper
これらのキーワードは、リンクの生成、モデル バインド パラメーター、またはトップ レベルのプロパティには使用できません。
HTTP 動詞テンプレート
ASP.NET Core には、次の HTTP 動詞テンプレートがあります。
ルート テンプレート
ASP.NET Core には、次のルート テンプレートがあります。
- HTTP 動詞テンプレートのすべては、ルート テンプレートにもなります。
- [Route]
Http 動詞属性を使用する属性ルーティング
次のようなコントローラーがあるとします。
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int2/{id}")] // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードでは以下の操作が行われます。
- 各アクションには
[HttpGet]
属性が含まれているので、HTTP GET 要求への一致のみを制限します。 GetProduct
アクションには"{id}"
テンプレートが含まれるため、id
がコントローラーの"api/[controller]"
テンプレートに追加されます。 メソッド テンプレートは"api/[controller]/{id}"
です。 したがって、このアクションは、/api/test2/xyz
、/api/test2/123
、/api/test2/{any string}
などの形式の GET 要求にのみ一致します。[HttpGet("{id}")] // GET /api/test2/xyz public IActionResult GetProduct(string id) { return ControllerContext.MyDisplayRouteInfo(id); }
GetIntProduct
アクションは"int/{id:int}"
テンプレートを組み込みます。 テンプレート:int
の部分で、整数に変換できる文字列にid
ルート値を制限します。/api/test2/int/abc
への Get 要求は、- このアクションと一致しません。
- 404 Not Found エラーを返します。
[HttpGet("int/{id:int}")] // GET /api/test2/int/3 public IActionResult GetIntProduct(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
GetInt2Product
アクションには、テンプレートの{id}
が含まれますが、整数に変換できる値にid
を制限しません。/api/test2/int2/abc
への Get 要求は、- このルートと一致します。
- モデル バインドは整数への
abc
の変換に失敗します。 メソッドのid
パラメーターは整数です。 - モデル バインドが
abc
を整数に変換できなかったため、400 Bad Request を返します。[HttpGet("int2/{id}")] // GET /api/test2/int2/3 public IActionResult GetInt2Product(int id) { return ControllerContext.MyDisplayRouteInfo(id); }
属性ルーティングでは、HttpPostAttribute、HttpPutAttribute、HttpDeleteAttribute などの HttpMethodAttribute 属性を使用できます。 HTTP 動詞属性はすべて、ルート テンプレートを受け入れます。 次の例では、同じルート テンプレートと一致する 2 つのアクションが示されています。
[ApiController]
public class MyProductsController : ControllerBase
{
[HttpGet("/products3")]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpPost("/products3")]
public IActionResult CreateProduct(MyProduct myProduct)
{
return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
}
}
URL パス /products3
を使用すると、
- HTTP 動詞が
GET
の場合にMyProductsController.ListProducts
アクションが実行されます。 - HTTP 動詞が
POST
の場合にMyProductsController.CreateProduct
アクションが実行されます。
REST API を構築する場合、アクションですべての HTTP メソッドが受け入れられるため、アクション メソッドで [Route(...)]
を使用する必要があるのはまれです。 より具体的な HTTP 動詞属性を使って、API がサポートするものを正確に指定することをお勧めします。 REST API のクライアントは、パスおよび HTTP 動詞と特定の論理操作のマップを理解していることを期待されます。
REST API では、属性ルーティングを使用して、HTTP 動詞で操作を表現するリソースのセットとしてアプリの機能をモデル化する必要があります。 つまり、同じ論理リソース上の多くの操作 (たとえば GET や POST) で、同じ URL が使用されます。 属性ルーティングでは、API のパブリック エンドポイント レイアウトを慎重に設計するために必要となるコントロールのレベルが提供されます。
属性ルートは特定のアクションに適用されるため、ルート テンプレート定義の一部として簡単にパラメーターを必須にできます。 次の例では、id
は URL パスの一部として必須です。
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
Products2ApiController.GetProduct(int)
アクションは、
/products2/3
のような URL パスで実行されます。- URL パス
/products2
では実行されません。
[Consumes] 属性を使用すると、アクションでサポートされる要求のコンテンツの種類を制限できます。 詳細については、「Consumes 属性を使ってサポートされる要求のコンテンツの種類を定義する」を参照してください。
ルート テンプレートと関連するオプションについて詳しくは、「ルーティング」をご覧ください。
[ApiController]
の詳細については、ApiController 属性に関する記事を参照してください。
ルート名
次のコードでは、Products_List
のルート名を定義しています。
[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
ルート名を使うと、特定のルートに基づいて URL を生成できます。 ルート名には次が適用されます。
- ルーティングの URL 照合動作には影響しません。
- URL の生成にのみ使用されます。
ルート名は、アプリケーション全体で一意である必要があります。
上のコードと、id
パラメーターを省略可能 ({id?}
) として定義する規則の既定ルートを比較します。 API を厳密に指定するこの機能には、/products
と /products/5
を異なるアクションに対してディスパッチできるといった利点があります。
属性ルートの組み合わせ
属性ルーティングの反復を少なくするため、コントローラーのルート属性は個々のアクションでのルート属性と結合されます。 コントローラーで定義されているルート テンプレートが、アクションのルート テンプレートの前に付加されます。 ルート属性をコントローラーに配置すると、コントローラーのすべてのアクションが属性ルーティングを使います。
[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
[HttpGet]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
前の例の場合:
- URL パス
/products
はProductsApi.ListProducts
と一致させることができます。 - URL パス
/products/5
はProductsApi.GetProduct(int)
と一致させることができます。
どちらのアクションも、[HttpGet]
属性でマークされているため、HTTP GET
だけと一致します。
/
または ~/
で始まるアクションに適用されるルート テンプレートは、コントローラーに適用されるルート テンプレートと結合されません。 次の例は、既定ルートと同様の URL パスのセットと一致します。
[Route("Home")]
public class HomeController : Controller
{
[Route("")]
[Route("Index")]
[Route("/")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
[Route("About")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
次の表では、上のコードの [Route]
属性について説明します。
属性 | [Route("Home")] との組み合わせ |
ルート テンプレートを定義 |
---|---|---|
[Route("")] |
はい | "Home" |
[Route("Index")] |
はい | "Home/Index" |
[Route("/")] |
無効 | "" |
[Route("About")] |
はい | "Home/About" |
属性ルートの順序
ルーティングによってツリーが構築され、すべてのエンドポイントが同時に照合されます。
- ルート エントリは、理想的な順序で配置されているかのように動作します。
- 最も具体的なルートは、より一般的なルートの前に実行される可能性があります。
たとえば、blog/search/{topic}
のような属性ルートは、blog/{*article}
のような属性ルートよりも具体的です。 blog/search/{topic}
ルートの優先順位は、より具体的なので、既定では高くなります。 規則ルーティングを使うときは、開発者が目的の順序でルートを配置する必要があります。
属性ルートでは、Order プロパティを使用して順番を構成できます。 フレームワークで提供されるすべてのルート属性には、Order
が含まれます。 ルートは、Order
プロパティの昇順に従って処理されます。 既定の順序は 0
です。 Order = -1
でルートを設定する場合、順序が設定されていないルートの前に実行されます。 Order = 1
でルートを設定する場合、既定のルート順序の後に実行されます。
Order
には依存しないでください。 アプリの URL 空間で正しくルーティングするために明示的な順序値が必要な場合、クライアントの混乱を招く可能性があります。 一般に、属性ルーティングは URL 照合で正しいルートを選びます。 URL の生成に使われる既定の順序がうまくいかない場合は、通常、オーバーライドとしてルート名を使う方が、Order
プロパティを適用するより簡単です。
次の 2 つのコントローラーを考え、両方で /home
と一致するルートを定義します。
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
public class MyDemoController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードで /home
を要求すると、次のような例外がスローされます。
AmbiguousMatchException: The request matched multiple endpoints. Matches:
WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex
ルート属性の 1 つに Order
を追加すると、あいまいさが解決されます。
[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
return ControllerContext.MyDisplayRouteInfo();
}
上のコードでは、/home
は HomeController.Index
エンドポイントを実行します。 MyDemoController.MyIndex
を取得するには、/home/MyIndex
を要求します。 注:
- 上のコードは一例であり、ルーティング設計は不十分です。
Order
プロパティの説明のために使用しました。 Order
プロパティはあいまいさを解決しますが、そのテンプレートを照合できません。[Route("Home")]
テンプレートを削除することをお勧めします。
Razor Pages でのルート順序については、Razor Pages のルートとアプリの規則に関する記事の「ルート順序」を参照してください。
場合によっては、あいまいなルートで HTTP 500 エラーが返されます。 ログを使用して、AmbiguousMatchException
の原因となるエンドポイントを確認します。
ルート テンプレートでのトークンの置換 ([controller]、[action]、[area])
利便性のため、属性ルートではトークンを角かっこ ([
、]
) で囲むことによる "トークンの置換" がサポートされています。 トークン [action]
、[area]
、および [controller]
は、ルートが定義されているアクションのアクション名、区分名、コントローラー名の値に置き換えられます。
[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードでは以下の操作が行われます。
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
/Products0/List
と一致します
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
/Products0/Edit/{id}
と一致します
属性ルート作成の最後のステップで、トークンの置換が発生します。 上の例では、次のコードと同じ動作が実行されます。
public class Products20Controller : Controller
{
[HttpGet("[controller]/[action]")] // Matches '/Products20/List'
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("[controller]/[action]/{id}")] // Matches '/Products20/Edit/{id}'
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
英語以外の言語でこの記事をお読みになっていて、コードのコメントをネイティブ言語でご覧になりたい場合は、この GitHub ディスカッション イシューでお知らせください。
属性ルートを継承と組み合わせることもできます。 これをトークンの置換と組み合わせると強力です。 トークンの置換は、属性ルートで定義されているルート名にも適用されます。
[Route("[controller]/[action]", Name="[controller]_[action]")]
では、アクションごとに一意のルート名が生成されます。
[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}
public class Products11Controller : MyBase2Controller
{
[HttpGet] // /api/products11/list
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}
[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
リテラル トークン置換区切り文字 [
または ]
と一致させるためには、その文字を繰り返すことでエスケープします ([[
または ]]
)。
パラメーター トランスフォーマーを使用してトークンの置換をカスタマイズする
トークンの置換は、パラメーター トランスフォーマーを使用してカスタマイズできます。 パラメーター トランスフォーマーは IOutboundParameterTransformer を実装し、パラメーターの値を変換します。 たとえば、SlugifyParameterTransformer
パラメーター トランスフォーマーでは、SubscriptionManagement
のルート値が subscription-management
に変更されます。
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string TransformOutbound(object value)
{
if (value == null) { return null; }
return Regex.Replace(value.ToString(),
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
RouteTokenTransformerConvention は、次のようなアプリケーション モデルの規則です。
- パラメーター トランスフォーマーをアプリケーションのすべての属性ルートに適用します。
- 置き換えられる属性ルートのトークン値をカスタマイズします。
public class SubscriptionManagementController : Controller
{
[HttpGet("[controller]/[action]")]
public IActionResult ListAll()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上の /subscription-management/list-all
メソッドでは ListAll
と一致します。
RouteTokenTransformerConvention
は、ConfigureServices
でオプションとして登録されます。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
});
}
Slug の定義については、 Slug の MDN Web ドキュメントを参照してください。
警告
System.Text.RegularExpressions を使用して信頼できない入力を処理するときは、タイムアウトを渡します。 悪意のあるユーザーが RegularExpressions
に入力を提供して、サービス拒否攻撃を行う可能性があります。 RegularExpressions
を使用する ASP.NET Core フレームワーク API は、タイムアウトを渡します。
複数の属性ルート
属性ルーティングでは、同じアクションに到達する複数のルートの定義がサポートされています。 これの最も一般的な使用方法は、次の例で示すように、"既定の規則ルート" の動作を模倣することです。
[Route("[controller]")]
public class Products13Controller : Controller
{
[Route("")] // Matches 'Products13'
[Route("Index")] // Matches 'Products13/Index'
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}
コントローラーに複数のルート属性を配置すると、それぞれが、アクション メソッドの各ルート属性と結合します。
[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
[HttpPost("Buy")] // Matches 'Products6/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products6/Checkout' and 'Store/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
すべての HTTP 動詞ルート制約は、IActionConstraint
を実装します。
IActionConstraint を実装する複数のルート属性が 1 つのアクションに配置されている場合:
- 各アクション制約は、コントローラーに適用されたルート テンプレートと組み合わされます。
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
[HttpPut("Buy")] // Matches PUT 'api/Products7/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products7/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
アクションで複数のルートを使用すると便利で強力な場合があります。アプリの URL 空間の基本的な状態を維持し、明確に定義しておくことをお勧めします。 アクションで複数のルートを使うのは、必要な場合 (既存のクライアントをサポートする、など) だけにしてください。
属性ルートの省略可能なパラメーター、既定値、制約を指定する
属性ルートでは、省略可能なパラメーター、既定値、および制約の指定に関して、規則ルートと同じインライン構文がサポートされています。
public class Products14Controller : Controller
{
[HttpPost("product14/{id:int}")]
public IActionResult ShowProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
上のコードでは、[HttpPost("product14/{id:int}")]
はルート制約を適用します。 Products14Controller.ShowProduct
アクションは、/product14/3
のような URL パスによってのみ照合されます。 ルート テンプレートの部分 {id:int}
は、そのセグメントを整数のみに制限します。
ルート テンプレートの構文について詳しくは、「ルート テンプレート参照」をご覧ください。
IRouteTemplateProvider を使用したカスタム ルート属性
すべてのルート属性は IRouteTemplateProvider を実装します。 ASP.NET Core ランタイムは、
- アプリの起動時に、コントローラー クラスとアクション メソッドの属性を検索します。
IRouteTemplateProvider
を実装する属性を使用して、ルートの初期セットを構築します。
IRouteTemplateProvider
を実装して、カスタム ルート属性を定義します。 各 IRouteTemplateProvider
では、カスタム ルート テンプレート、順序、名前を使って 1 つのルートを定義できます。
public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
public string Template => "api/[controller]";
public int? Order => 2;
public string Name { get; set; }
}
[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上の Get
メソッドでは Order = 2, Template = api/MyTestApi
が返されます。
アプリケーション モデルを使用した属性ルートのカスタマイズ
アプリケーション モデルは、
- 起動時に作成されるオブジェクト モデルです。
- アプリでアクションをルーティングしたり、実行したりするために ASP.NET Core によって使用されるすべてのメタデータが含まれます。
アプリケーション モデルには、ルート属性から収集されるすべてのデータが含まれます。 ルート属性からのデータは、IRouteTemplateProvider
実装によって提供されます。 表記規則:
- アプリケーション モデルを変更して、ルーティングの動作をカスタマイズするために、記述できます。
- アプリの起動時に読み込まれます。
このセクションでは、アプリケーション モデルを使ってルーティングをカスタマイズする基本的な例を示します。 次のコードでは、ルートがプロジェクトのフォルダー構造とほぼ同じになるようにします。
public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
private readonly string _baseNamespace;
public NamespaceRoutingConvention(string baseNamespace)
{
_baseNamespace = baseNamespace;
}
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
var namespc = controller.ControllerType.Namespace;
if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]/[action]/{id?}");
foreach (var selector in controller.Selectors)
{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}
次のコードでは、属性でルーティングされているコントローラーに namespace
規則が適用されないようにします。
public void Apply(ControllerModel controller)
{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel != null);
if (hasRouteAttributes)
{
return;
}
たとえば、次のコントローラーでは NamespaceRoutingConvention
を使用しません。
[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
// /managers/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
return Content($"Index- template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
NamespaceRoutingConvention.Apply
メソッド:
- コントローラーが属性でルーティングされている場合は、何も実行しません。
namespace
に基づいてコントローラー テンプレートを設定します。ベースnamespace
は削除されます。
NamespaceRoutingConvention
は Startup.ConfigureServices
で適用できます。
namespace My.Application
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(Startup).Namespace));
});
}
// Remaining code ommitted for brevity.
たとえば、次のようなコントローラーがあるとします。
using Microsoft.AspNetCore.Mvc;
namespace My.Application.Admin.Controllers
{
public class UsersController : Controller
{
// GET /admin/controllers/users/index
public IActionResult Index()
{
var fullname = typeof(UsersController).FullName;
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var path = Request.Path.Value;
return Content($"Path: {path} fullname: {fullname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"Path: {path} ID:{id}");
}
}
}
上のコードでは以下の操作が行われます。
- ベース
namespace
はMy.Application
です。 - 上のコントローラーの完全な名前は
My.Application.Admin.Controllers.UsersController
です。 NamespaceRoutingConvention
によって、コントローラー テンプレートがAdmin/Controllers/Users/[action]/{id?
に設定されます。
NamespaceRoutingConvention
は、コントローラーの属性としても適用できます。
[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
// /admin/controllers/test/index
public IActionResult Index()
{
var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var actionname = ControllerContext.ActionDescriptor.ActionName;
return Content($"Action- {actionname} template:{template}");
}
public IActionResult List(int? id)
{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}
混合ルーティング: 属性ルーティングと規則ルーティング
ASP.NET Core アプリケーションでは、規則ルーティングと属性ルーティングを併用できます。 一般に、ブラウザーに HTML ページを提供するコントローラーには規則ルートを使い、REST API を提供するコントローラーには属性ルーティングを使います。
アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性ルーティングされるようになります。 属性ルートが定義されているアクションには規則ルートでは到達できず、規則ルートが定義されているアクションには属性ルートでは到達できません。 コントローラーの任意のルート属性により、コントローラー属性のすべてのアクションがルーティングされます。
属性ルーティングと規則ルーティングで、同じルーティング エンジンが使用されます。
URL 生成とアンビエント値
アプリでは、ルーティングの URL 生成機能を使って、アクションへの URL リンクを生成できます。 URL を生成すると URL をハードコーディングする必要がなくなり、コードの堅牢性と保守性が向上します。 このセクションでは、MVC によって提供される URL 生成機能について説明します。URL 生成のしくみに関する基本だけを取り上げます。 URL の生成について詳しくは、「ルーティング」をご覧ください。
IUrlHelper インターフェイスは、MVC と URL 生成のルーティングの間にあるインフラストラクチャの基盤となる要素です。 IUrlHelper
のインスタンスは、コントローラー、ビュー、およびビュー コンポーネントの Url
プロパティを使って使用できます。
次の例の IUrlHelper
インターフェイスは、別のアクションへの URL を生成するために Controller.Url
プロパティを介して使われています。
public class UrlGenerationController : Controller
{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
アプリが既定の規則ルートを使っている場合、url
変数の値は URL パス文字列 /UrlGeneration/Destination
になります。 この URL パスは、次の組み合わせで、ルーティングによって作成されます。
- アンビエント値と呼ばれる、現在の要求のルート値。
Url.Action
に渡され、それらの値をルート テンプレートに置き換える値。
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}
result: /UrlGeneration/Destination
ルート テンプレートの各ルート パラメーターの値は、一致する名前と値およびアンビエント値によって置換されます。 値を持たないルート パラメーターは、
- 既定値がある場合は、それを使用できます。
- 省略可能な場合はスキップできます (たとえば、ルート テンプレート
{controller}/{action}/{id?}
のid
)。
必須のルート パラメーターに対応する値がない場合、URL の生成は失敗します。 ルートの URL 生成が失敗した場合、すべてのルートが試されるまで、または一致が見つかるまで、次のルートが試されます。
上の Url.Action
の例では、規則ルーティングを想定しています。 URL 生成は属性ルーティングでも同じように動作しますが、概念は異なります。 規則ルーティングでは、
- ルート値は、テンプレートを展開するために使用されます。
controller
とaction
のルート値は通常、そのテンプレートに表示されます。 これは、ルーティングによって一致した URL が規則に準拠しているために機能します。
次の例では、属性ルーティングが使用されています。
public class UrlGenerationAttrController : Controller
{
[HttpGet("custom")]
public IActionResult Source()
{
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
上のコードの Source
アクションによって、custom/url/to/destination
が生成されます。
LinkGenerator は、IUrlHelper
の代わりに ASP.NET Core 3.0 で追加されました。 LinkGenerator
は同様のより柔軟な機能を提供します。 IUrlHelper
の各メソッドには、LinkGenerator
に対応するメソッド ファミリが存在します。
アクション名による URL の生成
Url.Action、LinkGenerator.GetPathByAction、および関連するすべてのオーバーロードは、コントローラー名とアクション名を指定して、ターゲット エンドポイントを生成するように設計されています。
Url.Action
を使用する場合、controller
と action
の現在のルート値は、ランタイムによって提供されます。
controller
およびaction
の値は、アンビエント値と値の両方の一部です。Url.Action
メソッドは、常にaction
とcontroller
の現在の値を使い、現在のアクションにルーティングする URL パスを生成します。
ルーティングの際に、URL の生成時に指定されなかった情報を、アンビエント値を使って埋めようとします。 アンビエント値 { a = Alice, b = Bob, c = Carol, d = David }
を使用する {a}/{b}/{c}/{d}
のようなルートを考えてみましょう。
- ルーティングには、URL を生成するための十分な情報が含まれており、追加の値は必要ありません。
- すべてのルート パラメーターに値があるため、ルーティングには十分な情報があります。
値 { d = Donovan }
が追加された場合、
- 値
{ d = David }
は無視されます。 - 生成された URL パスは
Alice/Bob/Carol/Donovan
です。
警告: URL パスは階層的です。 上の例で、値 { c = Cheryl }
が追加されている場合、
- 値
{ c = Carol, d = David }
は両方とも無視されます。 d
の値は存在せず、URL の生成は失敗します。- URL を生成するには、
c
とd
に必要な値を指定する必要があります。
既定のルート {controller}/{action}/{id?}
では、この問題が発生する可能性があります。 Url.Action
では常に controller
と action
の値が明示的に指定されているため、実際にはこの問題はめったに起こりません。
Url.Action のいくつかのオーバーロードでは、ルート値のオブジェクトを受け取って、controller
と action
以外のルート パラメーターの値を提供します。 ルート値オブジェクトは、id
と共によく使用されます。 たとえば、「 Url.Action("Buy", "Products", new { id = 17 })
」のように入力します。 ルート値オブジェクトは、
- 慣例により、通常は匿名型のオブジェクトです。
IDictionary<>
または POCO になります。
ルート パラメーターと一致しないすべての追加ルート値は、クエリ文字列に格納されます。
public IActionResult Index()
{
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url);
}
上のコードによって /Products/Buy/17?color=red
が生成されます。
次のコードでは、絶対 URL が生成されます。
public IActionResult Index2()
{
var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
// Returns https://localhost:5001/Products/Buy/17
return Content(url);
}
絶対 URL を作成するには、次のいずれかを使用します。
protocol
を受け入れるオーバーロード。 たとえば、上のコードを使用します。- 既定で絶対 URI を生成する LinkGenerator.GetUriByAction。
ルートによる URL の生成
上のコードでは、コントローラーとアクション名を渡すことによる URL の生成を示しました。 IUrlHelper
では、Url.RouteUrl ファミリ メソッドも提供されています。 これらのメソッドは、Url.Action と似ていますが、action
と controller
の現在の値をルート値にコピーしません。 Url.RouteUrl
の最も一般的な使用方法は、次のとおりです。
- URL を生成するルート名を指定します。
- 通常、コントローラーまたはアクション名は指定しません。
public class UrlGeneration2Controller : Controller
{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}
[HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
次の Razor ファイルでは、Destination_Route
への HTML リンクが生成されます。
<h1>Test Links</h1>
<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>
HTML および Razor での URL の生成
IHtmlHelper は HtmlHelper メソッドの Html.BeginForm と Html.ActionLink を提供し、それぞれ <form>
と <a>
要素を生成します。 これらのメソッドは Url.Action メソッドを使って URL を生成するので、同じような引数を受け取ります。 HtmlHelper
の Url.RouteUrl
コンパニオンは、同様の機能を持つ Html.BeginRouteForm
と Html.RouteLink
です。
TagHelper は、form
TagHelper と <a>
TagHelper を使って URL を生成します。 これらはどちらも、実装に IUrlHelper
を使います。 詳しくは、フォームのタグ ヘルパーに関するページをご覧ください。
ビューの内部では、Url
プロパティを使って任意のアドホック URL 生成に IUrlHelper
を使うことができます (上の記事では説明されていません)。
アクションの結果での URL の生成
上の例では、コントローラーで IUrlHelper
を使用する方法を示しました。 コントローラーでの最も一般的な使用方法は、アクションの結果の一部として URL を生成する方法です。
基底クラス ControllerBase および Controller では、別のアクションを参照するアクション結果用の便利なメソッドが提供されています。 一般的な使用方法の 1 つは、ユーザー入力を受け付けた後でリダイレクトする方法です。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
if (ModelState.IsValid)
{
// Update DB with new details.
ViewData["Message"] = $"Successful edit of customer {id}";
return RedirectToAction("Index");
}
return View(customer);
}
RedirectToAction や CreatedAtAction のようなアクション結果ファクトリ メソッドは、IUrlHelper
のメソッドに似たパターンに従います。
専用規則ルートの特殊なケース
規則ルーティングでは、専用規則ルートと呼ばれる特殊なルート定義を使うことができます。 次の例の blog
という名前のルートが専用規則ルートです。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
endpoints.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
上のルート定義を使うと、Url.Action("Index", "Home")
は default
ルートで URL パス /
を生成します。なぜでしょうか。 ルート値 { controller = Home, action = Index }
は blog
を使って URL を生成するのに十分であり、結果は /blog?action=Index&controller=Home
になるように思われます。
専用規則ルートは、URL の生成でルートの一致範囲が広くなりすぎるのを防ぐために対応するルート パラメーターを持たない既定値の特別な動作に依存します。 この場合、既定値は { controller = Blog, action = Article }
であり、controller
と action
はどちらもルート パラメーターとして表示されません。 ルーティングが URL 生成を実行するとき、指定された値は既定値と一致する必要があります。 値 { controller = Home, action = Index }
は { controller = Blog, action = Article }
と一致しないため、blog
を使った URL の生成は失敗します。 そして、ルーティングはフォールバックして default
を試み、これは成功します。
Areas
区分は、関連する機能を個別のグループとしてまとめるために使用される MVC の機能です。
- コントローラー アクションのルーティング名前空間。
- ビュー用のフォルダー構造。
区分を使うと、アプリは同じ名前の複数のコントローラーを持つことができます (ただし、区分が異なる場合)。 区分を使い、別のルート パラメーター area
を controller
および action
に追加することで、ルーティングのための階層を作成します。 このセクションでは、ルーティングと区分がどのように相互作用するかについて説明します。 区分をビューで使用する方法の詳細については、区分に関する記事を参照してください。
次の例では、既定の規則ルートと、Blog
という名前の area
に対する area
ルートを使うように、MVC を構成しています。
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});
上のコードでは、"blog_route"
を作成するために MapAreaControllerRoute が呼び出されています。 2 番目のパラメーター "Blog"
は区分名です。
/Manage/Users/AddUser
のような URL パスを照合すると、"blog_route"
ルートではルート値 { area = Blog, controller = Users, action = AddUser }
が生成されます。 area
ルート値は、area
の既定値によって生成されます。 MapAreaControllerRoute
によって作成されるルートは、次と等しくなります。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});
MapAreaControllerRoute
は、指定された区分名 (この場合はBlog
) を使い、既定値と、area
に対する制約の両方からルートを作成します。 既定値はルートが常に { area = Blog, ... }
を生成することを保証し、制約では URL を生成するために値 { area = Blog, ... }
が必要です。
規則ルーティングは順序に依存します。 通常、区分のあるルートは区分を持たないルートより具体的なので、区分のあるルートは前の方に配置する必要があります。
上の例を使うと、ルート値 { area = Blog, controller = Users, action = AddUser }
は次のアクションと一致します。
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
[Area] 属性は、コントローラーが区分の一部であることを示します。 このコントローラーは、この Blog
区分にあります。 [Area]
属性を持たないコントローラーはどの区分のメンバーでもなく、area
ルート値がルーティングによって提供されたときは一致しません。 次の例では、リストの最初のコントローラーだけがルート値 { area = Blog, controller = Users, action = AddUser }
と一致できます。
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
// GET /zebra/users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
// GET /users/adduser
public IActionResult AddUser()
{
var area = ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName = ControllerContext.ActionDescriptor.ControllerName;
return Content($"area name:{area}" +
$" controller:{controllerName} action name: {actionName}");
}
}
}
各コントローラーの名前空間は、完全を期すためにここに示されています。 上のコントローラーで同じ名前空間が使用されている場合は、コンパイラ エラーが発生します。 クラスの名前空間は、MVC のルーティングに対して影響を与えません。
最初の 2 つのコントローラーは区分のメンバーであり、それぞれの区分名が area
ルート値によって提供された場合にのみ一致します。 3 番目のコントローラーはどの区分のメンバーでもなく、ルーティングによって area
の値が提供されない場合にのみ一致します。
"値なし" との一致では、area
の値がないことは、area
の値が null または空の文字列であることと同じです。
区分の内部でアクションを実行するとき、area
のルート値は、URL の生成に使うルーティングのアンビエント値として利用できます。 つまり、既定では、次の例で示すように、区分は URL の生成に対する "付箋" として機能します。
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute(name: "duck_route",
areaName: "Duck",
pattern: "Manage/{controller}/{action}/{id?}");
endpoints.MapControllerRoute(name: "default",
pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
// GET /Manage/users/GenerateURLInArea
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area.
var url = Url.Action("Index", "Home");
// Returns /Manage/Home/Index
return Content(url);
}
// GET /Manage/users/GenerateURLOutsideOfArea
public IActionResult GenerateURLOutsideOfArea()
{
// Uses the empty value for area.
var url = Url.Action("Index", "Home", new { area = "" });
// Returns /Manage
return Content(url);
}
}
}
次のコードでは /Zebra/Users/AddUser
への URL が生成されます。
public class HomeController : Controller
{
public IActionResult About()
{
var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
return Content($"URL: {url}");
}
アクション定義
NonAction 属性が設定されているものを除き、コントローラーのパブリック メソッドはアクションです。
サンプル コード
- MyDisplayRouteInfo は Rick.Docs.Samples.RouteInfo NuGet パッケージによって提供され、ルート情報が表示されます。
- サンプル コードを表示またはダウンロードします (ダウンロード方法)。
デバッグ診断
詳細なルーティング診断出力を行うには、Logging:LogLevel:Microsoft
を Debug
に設定してください。 開発環境では、appsettings.Development.json
でログ レベルを次のように設定します。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
ASP.NET Core