ASP.NET Core でのコントローラー アクションへのルーティング

作成者: Ryan NowakKirk LarkinRick Anderson

ASP.NET Core コントローラーは、ルーティング ミドルウェアを使って、受信した要求の URL を照合し、アクションにマップします。 ルート テンプレートは、

  • 属性の起動時または起動時 Program.cs に定義されます。
  • URL パスとアクションの照合方法が記述されています。
  • リンクの URL を生成するために使用されます。 生成されたリンクは通常、応答で返されます。

アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性でルーティングされるようになります。 詳しくは、「混合ルーティング」をご覧ください。

このドキュメントでは、

  • MVC とルーティングの間の相互作用について説明します。
    • 一般的な MVC アプリでルーティング機能を利用する方法。
    • 以下の 2 つについて説明します。
      • 通常は、コントローラーとビューで使用される規則ルーティング
      • API でREST使用される属性ルーティング。 主に API のルーティング REST に関心がある場合は、「API の 属性ルーティング REST 」 セクションに進んでください。
    • 高度なルーティングについて詳しくは、ルーティングに関する記事を参照してください。
  • エンドポイント ルーティングと呼ばれる既定のルーティング システムについて説明します。 互換性を確保するために、以前のバージョンのルーティングでコントローラーを使用することができます。 手順については、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);
        }
    }
    

    MyDisplayRouteInfoRick.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 } を生成します。

controlleraction の値に、既定値が使用されます。 URL パスに対応するセグメントがないため、id は値を生成しません。 HomeControllerIndex アクションが存在する場合のみ、/ は一致します。

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?}");

重要

ルーティングは、UseRoutingUseEndpoints ミドルウェアを使用して構成します。 コントローラーを使用するには、

通常は、アプリで UseRouting または UseEndpoints を呼び出す必要はありません。 WebApplicationBuilder は、Program.cs に追加されたミドルウェアを UseRoutingUseEndpoints でラップするミドルウェア パイプラインを構成します。 詳細については、「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 つのルートがあれば十分です。

MapControllerRouteMapAreaRoute では、

  • それぞれが呼び出された順序に基づいて、それぞれのエンドポイントに順序値が自動的に割り当てられます。

ASP.NET Core でのエンドポイントのルーティングは、

  • ルートの概念がありません。
  • 拡張性の実行に対する順序付けの保証は提供されません。すべてのエンドポイントは一度に処理されます。

ログを有効にすると、Route など、組み込みのルーティング実装で要求を照合するしくみを確認できます。

属性ルーティングについては、このドキュメントの後で説明します。

複数の規則ルート

より多くの呼び出しを追加することによって、複数の従来のルートMapControllerRouteMapAreaControllerRoute構成できます。 これにより、次のように、複数の規則を定義したり、特定のアクションに専用の規則ルートを追加したりすることができます。

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

上の例の blog が、専用の規則ルートです。 これは、次の理由で専用の規則ルートと呼ばれます。

controlleraction は、パラメーターとしてルート テンプレート "blog/{*article}" に表示されないため、

  • 既定値 { controller = "Blog", action = "Article" } しか指定できません。
  • このルートは常にアクション BlogController.Article にマップされます。

/Blog/Blog/Article、および /Blog/{any-string} は、ブログ ルートに一致する唯一の URL パスです。

上記の例の場合:

  • blog ルートは最初に追加されるので、default ルートよりも照合の優先度が高くなります。
  • URL の一部として記事名を持つのが一般的な Slug スタイル ルーティングの例が示されています。

警告

ASP.NET Core では、ルーティングで以下は行いません。

  • "ルート" と呼ばれる概念の定義。 UseRouting では、ルートの照合がミドルウェア パイプラインに追加されます。 UseRouting ミドルウェアによって、アプリで定義されているエンドポイントのセットが調べられ、要求に基づいて最適な一致が選択されます。
  • IRouteConstraintIActionConstraint のような機能拡張の実行順序に関する保証。

詳細については、ルーティングに関する記事を参照してください。

規則ルーティングの順序

規則ルーティングは、アプリで定義されているアクションとコントローラーの組み合わせにのみ一致します。 これは、規則ルートが重複する場合に簡略化することを目的としています。 MapControllerRouteMapDefaultControllerRoute、および 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) は、要求が HTTP POST の場合に選択されます。
  • 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 によって異なります。

API の REST 属性ルーティング

REST API では、属性ルーティングを使用して、操作が HTTP 動詞で表されるリソースのセットとしてアプリの機能をモデル化する必要があります。

属性ルーティングでは、属性のセットを使ってアクションをルート テンプレートに直接マップします。 次のコードは API に一般的であり REST 、次のサンプルで使用されます。

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);
    }
}

次のコードでは、actioncontroller のトークン置換が使用されます。

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("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);
      }
      

属性ルーティングでは、HttpPostAttributeHttpPutAttributeHttpDeleteAttribute などの 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 アクションが実行されます。

API を REST 構築する場合、アクションがすべての HTTP メソッドを受け入れるため、アクション メソッドで使用 [Route(...)] する必要が生じることはまれです。 より具体的な HTTP 動詞属性を使って、API がサポートするものを正確に指定することをお勧めします。 API のクライアントは、特定の REST 論理操作にマップされるパスと 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 パス /productsProductsApi.ListProducts と一致させることができます。
  • URL パス /products/5ProductsApi.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")] Yes "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();
}

上のコードでは、/homeHomeController.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 は削除されます。

NamespaceRoutingConventionProgram.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}");
        }
    }
}

上のコードでは以下の操作が行われます。

  • ベース namespaceMy.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 ページを提供するコントローラーには従来のルートを使用し、API を提供 REST するコントローラーには属性ルーティングを使用するのが一般的です。

アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性ルーティングされるようになります。 属性ルートが定義されているアクションには規則ルートでは到達できず、規則ルートが定義されているアクションには属性ルートでは到達できません。 コントローラーのルート属性は、コントローラー属性のすべてのアクションをルーティングします。

属性ルーティングと規則ルーティングで、同じルーティング エンジンが使用されます。

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

ルート テンプレートの各ルート パラメーターの値は、一致する名前と値およびアンビエント値によって置換されます。 値を持たないルート パラメーターは、

  • 既定値がある場合は、それを使用できます。
  • 省略可能な場合はスキップできます たとえば、 id ルート テンプレート {controller}/{action}/{id?}からです。

必須のルート パラメーターに対応する値がない場合、URL の生成は失敗します。 ルートの URL 生成が失敗した場合、すべてのルートが試されるまで、または一致が見つかるまで、次のルートが試されます。

上の Url.Action の例では、規則ルーティングを想定しています。 URL 生成は属性ルーティングでも同じように動作しますが、概念は異なります。 規則ルーティングでは、

  • ルート値は、テンプレートを展開するために使用されます。
  • controlleraction のルート値は通常、そのテンプレートに表示されます。 これは、ルーティングによって一致した 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.ActionLinkGenerator.GetPathByAction、および関連するすべてのオーバーロードは、コントローラー名とアクション名を指定して、ターゲット エンドポイントを生成するように設計されています。

Url.Action を使用する場合、controlleraction の現在のルート値は、ランタイムによって提供されます。

  • controller および action の値は、アンビエント値と値の両方の一部です。 Url.Action メソッドは、常に actioncontroller の現在の値を使い、現在のアクションにルーティングする 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 を生成するには、cd に必要な値を指定する必要があります。

既定のルート {controller}/{action}/{id?} では、この問題が発生する可能性があります。 Url.Action では常に controlleraction の値が明示的に指定されているため、実際にはこの問題はめったに起こりません。

Url.Action のいくつかのオーバーロードでは、ルート値のオブジェクトを受け取って、controlleraction 以外のルート パラメーターの値を提供します。 ルート値オブジェクトは、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 と似ていますが、actioncontroller の現在の値をルート値にコピーしません。 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 の生成

IHtmlHelperHtmlHelper メソッドの Html.BeginFormHtml.ActionLink を提供し、それぞれ <form><a> 要素を生成します。 これらのメソッドは Url.Action メソッドを使って URL を生成するので、同じような引数を受け取ります。 HtmlHelperUrl.RouteUrl コンパニオンは、同様の機能を持つ Html.BeginRouteFormHtml.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);
}

RedirectToActionCreatedAtAction のようなアクション結果ファクトリ メソッドは、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 } であり、controlleraction はどちらもルート パラメーターとして表示されません。 ルーティングが URL 生成を実行するとき、指定された値は既定値と一致する必要があります。 値 { controller = Home, action = Index }{ controller = Blog, action = Article } と一致しないため、blog を使った URL の生成は失敗します。 そして、ルーティングはフォールバックして default を試み、これは成功します。

Areas

区分は、関連する機能を個別のグループとしてまとめるために使用される MVC の機能です。

  • コントローラー アクションのルーティング名前空間。
  • ビュー用のフォルダー構造。

区分を使うと、アプリは同じ名前の複数のコントローラーを持つことができます (ただし、区分が異なる場合)。 区分を使い、別のルート パラメーター areacontroller および 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 属性が設定されているものを除き、コントローラーのパブリック メソッドはアクションです。

サンプル コード

デバッグ診断

詳細なルーティング診断出力を行うには、Logging:LogLevel:MicrosoftDebug に設定してください。 開発環境では、appsettings.Development.json でログ レベルを次のように設定します。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

ASP.NET Core コントローラーは、ルーティング ミドルウェアを使って、受信した要求の URL を照合し、アクションにマップします。 ルート テンプレートは、

  • スタートアップ コードまたは属性で定義されています。
  • URL パスとアクションの照合方法が記述されています。
  • リンクの URL を生成するために使用されます。 生成されたリンクは通常、応答で返されます。

アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性でルーティングされるようになります。 詳しくは、「混合ルーティング」をご覧ください。

このドキュメントでは、

  • MVC とルーティングの間の相互作用について説明します。
    • 一般的な MVC アプリでルーティング機能を利用する方法。
    • 以下の 2 つについて説明します。
      • 通常は、コントローラーとビューで使用される規則ルーティング
      • API でREST使用される属性ルーティング。 主に API のルーティング REST に関心がある場合は、「API の 属性ルーティング REST 」 セクションに進んでください。
    • 高度なルーティングについて詳しくは、ルーティングに関する記事を参照してください。
  • エンドポイント ルーティングと呼ばれる、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);
        }
    }
    

    MyDisplayRouteInfoRick.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 } を生成します。

controlleraction の値に、既定値が使用されます。 URL パスに対応するセグメントがないため、id は値を生成しません。 HomeControllerIndex アクションが存在する場合のみ、/ は一致します。

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?}");

重要

ルーティングは、UseRoutingMapControllerRoute、および 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 つのルートがあれば十分です。

MapControllerRouteMapAreaRoute では、

  • それぞれが呼び出された順序に基づいて、それぞれのエンドポイントに順序値が自動的に割り当てられます。

ASP.NET Core 3.0 以降でのエンドポイントのルーティング:

  • ルートの概念がありません。
  • 拡張性の実行に対する順序付けの保証は提供されません。すべてのエンドポイントは一度に処理されます。

ログを有効にすると、Route など、組み込みのルーティング実装で要求を照合するしくみを確認できます。

属性ルーティングについては、このドキュメントの後で説明します。

複数の規則ルート

MapControllerRouteMapAreaControllerRoute の呼び出しをさらに追加することで、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 が、専用の規則ルートです。 これは、次の理由で専用の規則ルートと呼ばれます。

controlleraction は、パラメーターとしてルート テンプレート "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 ミドルウェアによって、アプリで定義されているエンドポイントのセットが調べられ、要求に基づいて最適な一致が選択されます。
  • IRouteConstraintIActionConstraint のような機能拡張の実行順序に関する保証。

詳細については、ルーティングに関する記事を参照してください。

規則ルーティングの順序

規則ルーティングは、アプリで定義されているアクションとコントローラーの組み合わせにのみ一致します。 これは、規則ルートが重複する場合に簡略化することを目的としています。 MapControllerRouteMapDefaultControllerRoute、および 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) は、要求が HTTP POST の場合に選択されます。
  • 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 によって異なります。

API の REST 属性ルーティング

REST API では、属性ルーティングを使用して、操作が HTTP 動詞で表されるリソースのセットとしてアプリの機能をモデル化する必要があります。

属性ルーティングでは、属性のセットを使ってアクションをルート テンプレートに直接マップします。 次 StartUp.Configure のコードは API に一般的であり REST 、次のサンプルで使用されます。

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);
    }
}

次のコードでは、actioncontroller のトークン置換が使用されます。

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("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);
      }
      

属性ルーティングでは、HttpPostAttributeHttpPutAttributeHttpDeleteAttribute などの 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 アクションが実行されます。

API を REST 構築する場合、アクションがすべての HTTP メソッドを受け入れるため、アクション メソッドで使用 [Route(...)] する必要が生じることはまれです。 より具体的な HTTP 動詞属性を使って、API がサポートするものを正確に指定することをお勧めします。 API のクライアントは、特定の REST 論理操作にマップされるパスと 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 パス /productsProductsApi.ListProducts と一致させることができます。
  • URL パス /products/5ProductsApi.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")] Yes "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();
}

上のコードでは、/homeHomeController.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 は削除されます。

NamespaceRoutingConventionStartup.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}");
        }
    }
}

上のコードでは以下の操作が行われます。

  • ベース namespaceMy.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 ページを提供するコントローラーには従来のルートを使用し、API を提供 REST するコントローラーには属性ルーティングを使用するのが一般的です。

アクションは、規則的にルーティングされるか、または属性でルーティングされます。 コントローラーまたはアクションにルートを配置すると、そのルートは属性ルーティングされるようになります。 属性ルートが定義されているアクションには規則ルートでは到達できず、規則ルートが定義されているアクションには属性ルートでは到達できません。 コントローラーの "任意" のルート属性により、コントローラー属性の "すべて" のアクションがルーティングされます。

属性ルーティングと規則ルーティングで、同じルーティング エンジンが使用されます。

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

ルート テンプレートの各ルート パラメーターの値は、一致する名前と値およびアンビエント値によって置換されます。 値を持たないルート パラメーターは、

  • 既定値がある場合は、それを使用できます。
  • 省略可能な場合はスキップできます たとえば、 id ルート テンプレート {controller}/{action}/{id?}からです。

必須のルート パラメーターに対応する値がない場合、URL の生成は失敗します。 ルートの URL 生成が失敗した場合、すべてのルートが試されるまで、または一致が見つかるまで、次のルートが試されます。

上の Url.Action の例では、規則ルーティングを想定しています。 URL 生成は属性ルーティングでも同じように動作しますが、概念は異なります。 規則ルーティングでは、

  • ルート値は、テンプレートを展開するために使用されます。
  • controlleraction のルート値は通常、そのテンプレートに表示されます。 これは、ルーティングによって一致した 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.ActionLinkGenerator.GetPathByAction、および関連するすべてのオーバーロードは、コントローラー名とアクション名を指定して、ターゲット エンドポイントを生成するように設計されています。

Url.Action を使用する場合、controlleraction の現在のルート値は、ランタイムによって提供されます。

  • controller および action の値は、アンビエント値と値の両方の一部です。 Url.Action メソッドは、常に actioncontroller の現在の値を使い、現在のアクションにルーティングする 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 を生成するには、cd に必要な値を指定する必要があります。

既定のルート {controller}/{action}/{id?} では、この問題が発生する可能性があります。 Url.Action では常に controlleraction の値が明示的に指定されているため、実際にはこの問題はめったに起こりません。

Url.Action のいくつかのオーバーロードでは、ルート値のオブジェクトを受け取って、controlleraction 以外のルート パラメーターの値を提供します。 ルート値オブジェクトは、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 と似ていますが、actioncontroller の現在の値をルート値にコピーしません。 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 の生成

IHtmlHelperHtmlHelper メソッドの Html.BeginFormHtml.ActionLink を提供し、それぞれ <form><a> 要素を生成します。 これらのメソッドは Url.Action メソッドを使って URL を生成するので、同じような引数を受け取ります。 HtmlHelperUrl.RouteUrl コンパニオンは、同様の機能を持つ Html.BeginRouteFormHtml.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);
}

RedirectToActionCreatedAtAction のようなアクション結果ファクトリ メソッドは、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 } であり、controlleraction はどちらもルート パラメーターとして表示されません。 ルーティングが URL 生成を実行するとき、指定された値は既定値と一致する必要があります。 値 { controller = Home, action = Index }{ controller = Blog, action = Article } と一致しないため、blog を使った URL の生成は失敗します。 そして、ルーティングはフォールバックして default を試み、これは成功します。

Areas

区分は、関連する機能を個別のグループとしてまとめるために使用される MVC の機能です。

  • コントローラー アクションのルーティング名前空間。
  • ビュー用のフォルダー構造。

区分を使うと、アプリは同じ名前の複数のコントローラーを持つことができます (ただし、区分が異なる場合)。 区分を使い、別のルート パラメーター areacontroller および 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 属性が設定されているものを除き、コントローラーのパブリック メソッドはアクションです。

サンプル コード

デバッグ診断

詳細なルーティング診断出力を行うには、Logging:LogLevel:MicrosoftDebug に設定してください。 開発環境では、appsettings.Development.json でログ レベルを次のように設定します。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}