ASP.NET Core 中的路由至控制器動作Routing to controller actions in ASP.NET Core

作者:Ryan NowakRick AndersonBy Ryan Nowak and Rick Anderson

ASP.NET Core MVC 使用路由中介軟體來比對內送要求的 URL,並將這些 URL 對應至動作。ASP.NET Core MVC uses the Routing middleware to match the URLs of incoming requests and map them to actions. 路由是在啟動程式碼或屬性中定義。Routes are defined in startup code or attributes. 路由描述 URL 路徑應該如何與動作進行比對。Routes describe how URL paths should be matched to actions. 路由也可用來產生回應中所送出的連結 URL。Routes are also used to generate URLs (for links) sent out in responses.

動作可以使用慣例路由或屬性路由。Actions are either conventionally routed or attribute routed. 將路由放在控制器或動作上,即可讓它使用屬性路由。Placing a route on the controller or the action makes it attribute routed. 如需詳細資訊,請參閱混合路由See Mixed routing for more information.

本文件將說明 MVC 與路由之間的互動,並說明一般 MVC 應用程式如何使用路由功能。This document will explain the interactions between MVC and routing, and how typical MVC apps make use of routing features. 如需進階路由的詳細資料,請參閱路由See Routing for details on advanced routing.

設定路由中介軟體Setting up Routing Middleware

Configure 方法中,您可能會看到類似如下的程式碼:In your Configure method you may see code similar to:

app.UseMvc(routes =>
{
   routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

UseMvc 呼叫內,MapRoute 是用來建立單一路由,稱為 default 路由。Inside the call to UseMvc, MapRoute is used to create a single route, which we'll refer to as the default route. 大多數 MVC 應用程式會使用具有類似於 default 路由之範本的路由。Most MVC apps will use a route with a template similar to the default route.

路由範本 "{controller=Home}/{action=Index}/{id?}" 可能符合某個 URL 路徑 (例如 /Products/Details/5),並將透過 Token 化路徑來擷取路由值 { controller = Products, action = Details, id = 5 }The route template "{controller=Home}/{action=Index}/{id?}" can match a URL path like /Products/Details/5 and will extract the route values { controller = Products, action = Details, id = 5 } by tokenizing the path. MVC 會嘗試尋找名為 ProductsController 的控制器,並執行動作 DetailsMVC will attempt to locate a controller named ProductsController and run the action Details:

public class ProductsController : Controller
{
   public IActionResult Details(int id) { ... }
}

請注意,在此範例中,模型繫結會在叫用此動作時,使用 id = 5 值將 id 參數設定為 5Note that in this example, model binding would use the value of id = 5 to set the id parameter to 5 when invoking this action. 如需詳細資料,請參閱模型繫結See the Model Binding for more details.

使用 default 路由:Using the default route:

routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

路由範本:The route template:

  • {controller=Home}Home 定義為預設的 controller{controller=Home} defines Home as the default controller

  • {action=Index}Index 定義為預設的 action{action=Index} defines Index as the default action

  • {id?}id 定義為選擇性{id?} defines id as optional

預設和選擇性路由參數不一定要全部出現在 URL 路徑中才算相符。Default and optional route parameters don't need to be present in the URL path for a match. 如需路由範本語法的詳細描述,請參閱路由範本參考See Route Template Reference for a detailed description of route template syntax.

"{controller=Home}/{action=Index}/{id?}" 可能符合 URL 路徑 /,並將產生路由值 { controller = Home, action = Index }"{controller=Home}/{action=Index}/{id?}" can match the URL path / and will produce the route values { controller = Home, action = Index }. controlleraction 的值使用預設值;id 不會產生值,因為 URL 路徑中沒有對應的區段。The values for controller and action make use of the default values, id doesn't produce a value since there's no corresponding segment in the URL path. MVC 會使用這些路由值來選取 HomeControllerIndex 動作:MVC would use these route values to select the HomeController and Index action:

public class HomeController : Controller
{
  public IActionResult Index() { ... }
}

使用此控制器定義和路由範本,就會對下列任何 URL 路徑執行 HomeController.Index 動作:Using this controller definition and route template, the HomeController.Index action would be executed for any of the following URL paths:

  • /Home/Index/17

  • /Home/Index

  • /Home

  • /

UseMvcWithDefaultRoute 方法很方便:The convenience method UseMvcWithDefaultRoute:

app.UseMvcWithDefaultRoute();

可用來取代:Can be used to replace:

app.UseMvc(routes =>
{
   routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

UseMvcUseMvcWithDefaultRoute 會將 RouterMiddleware 的執行個體新增至中介軟體管線。UseMvc and UseMvcWithDefaultRoute add an instance of RouterMiddleware to the middleware pipeline. MVC 不會直接與中介軟體互動,而是使用路由來處理要求。MVC doesn't interact directly with middleware, and uses routing to handle requests. MVC 會透過 MvcRouteHandler 的執行個體連線到路由。MVC is connected to the routes through an instance of MvcRouteHandler. UseMvc 內的程式碼類似如下:The code inside of UseMvc is similar to the following:

var routes = new RouteBuilder(app);

// Add connection to MVC, will be hooked up by calls to MapRoute.
routes.DefaultHandler = new MvcRouteHandler(...);

// Execute callback to register routes.
// routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

// Create route collection and add the middleware.
app.UseRouter(routes.Build());

UseMvc 不會直接定義任何路由,而是將預留位置新增至 attribute 路由的路由集合。UseMvc doesn't directly define any routes, it adds a placeholder to the route collection for the attribute route. 多載 UseMvc(Action<IRouteBuilder>) 可讓您新增自己的路由,同時支援屬性路由。The overload UseMvc(Action<IRouteBuilder>) lets you add your own routes and also supports attribute routing. UseMvc 及其所有變化會新增屬性路由的預留位置;不論您如何設定UseMvc,一律可以使用屬性路由。UseMvc and all of its variations adds a placeholder for the attribute route - attribute routing is always available regardless of how you configure UseMvc. UseMvcWithDefaultRoute 會定義預設路由並支援屬性路由。UseMvcWithDefaultRoute defines a default route and supports attribute routing. 屬性路由一節包含屬性路由的詳細資料。The Attribute Routing section includes more details on attribute routing.

慣例路由Conventional routing

default 路由:The default route:

routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

即為「慣例路由」的範例。is an example of a conventional routing. 我們將此樣式稱為「慣例路由」,因為它會建立 URL 路徑的「慣例」:We call this style conventional routing because it establishes a convention for URL paths:

  • 第一個路徑區段對應至控制器名稱the first path segment maps to the controller name

  • 第二個對應至動作名稱the second maps to the action name.

  • 第三個區段代表用來對應至模型實體的選擇性 idthe third segment is used for an optional id used to map to a model entity

使用此 default 路由,URL 路徑 /Products/List 會對應至 ProductsController.List 動作;而 /Blog/Article/17 會對應至 BlogController.ArticleUsing this default route, the URL path /Products/List maps to the ProductsController.List action, and /Blog/Article/17 maps to BlogController.Article. 此對應只會根據控制器和動作名稱,而不會根據命名空間、來源檔案位置或方法參數。This mapping is based on the controller and action names only and isn't based on namespaces, source file locations, or method parameters.

提示

慣例路由與預設路由的搭配使用可讓您快速建置應用程式,而不需要針對每個定義的動作產生新的 URL 模式。Using conventional routing with the default route allows you to build the application quickly without having to come up with a new URL pattern for each action you define. 針對具有 CRUD 樣式動作的應用程式,在控制器之間保持一致的 URL 有助於簡化程式碼,並讓 UI 更容易預測。For an application with CRUD style actions, having consistency for the URLs across your controllers can help simplify your code and make your UI more predictable.

警告

路由範本將 id 定義為選擇性,也就是您的動作可以直接執行,而不需要將識別碼當作 URL 的一部分提供。The id is defined as optional by the route template, meaning that your actions can execute without the ID provided as part of the URL. 如果省略 URL 中的 id,通常表示它會由模型繫結設定為 0,因此在符合 id == 0 的資料庫中找不到任何實體。Usually what will happen if id is omitted from the URL is that it will be set to 0 by model binding, and as a result no entity will be found in the database matching id == 0. 屬性路由可提供您更細微的控制,讓某些動作需要此識別碼,而其他動作則不需要。Attribute routing can give you fine-grained control to make the ID required for some actions and not for others. 依照慣例,本文件將會包含可能出現在正確使用中的選擇性參數 (例如 id)。By convention the documentation will include optional parameters like id when they're likely to appear in correct usage.

多個路由Multiple routes

您可以將更多呼叫新增至 MapRoute,以在 UseMvc 內新增多個路由。You can add multiple routes inside UseMvc by adding more calls to MapRoute. 這樣做可讓您定義多個慣例,或新增特定動作專用的慣例路由,例如:Doing so allows you to define multiple conventions, or to add conventional routes that are dedicated to a specific action, such as:

app.UseMvc(routes =>
{
   routes.MapRoute("blog", "blog/{*article}",
            defaults: new { controller = "Blog", action = "Article" });
   routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

這裡的 blog 路由是「專用慣例路由」,也就是它會使用慣例路由系統,但專用於特定動作。The blog route here is a dedicated conventional route, meaning that it uses the conventional routing system, but is dedicated to a specific action. 由於 controlleraction 並未作為參數出現在路由範本中,它們只能有預設值,因此此路由一律會對應至動作 BlogController.ArticleSince controller and action don't appear in the route template as parameters, they can only have the default values, and thus this route will always map to the action BlogController.Article.

路由集合中的路由已經過排序,並將依其新增順序進行處理。Routes in the route collection are ordered, and will be processed in the order they're added. 因此在此範例中,會先嘗試 blog 路由,再嘗試 default 路由。So in this example, the blog route will be tried before the default route.

注意

「專用慣例路由」通常會使用 {*article} 等 catch-all 路由參數,來擷取 URL 路徑的其餘部分。Dedicated conventional routes often use catch-all route parameters like {*article} to capture the remaining portion of the URL path. 這可能會讓路由變得「太窮盡」,也就是它會比對您想要讓其他路由比對的 URL。This can make a route 'too greedy' meaning that it matches URLs that you intended to be matched by other routes. 將這些「窮盡」路由放在路由表後面可解決此問題。Put the 'greedy' routes later in the route table to solve this.

後援Fallback

在要求處理過程中,MVC 會確認路由值是否可用來尋找應用程式中的控制器和動作。As part of request processing, MVC will verify that the route values can be used to find a controller and action in your application. 如果路由值未符合任何動作,則不會將此路由視為一個相符項目,並將嘗試下一個路由。If the route values don't match an action then the route isn't considered a match, and the next route will be tried. 這稱為「遞補」,其目的在於簡化慣例路由重疊的情況。This is called fallback, and it's intended to simplify cases where conventional routes overlap.

釐清動作Disambiguating actions

若透過路由符合兩個動作,MVC 必須釐清並選擇「最佳」候選項目,否則會擲回例外狀況。When two actions match through routing, MVC must disambiguate to choose the 'best' candidate or else throw an exception. 例如:For example:

public class ProductsController : Controller
{
   public IActionResult Edit(int id) { ... }

   [HttpPost]
   public IActionResult Edit(int id, Product product) { ... }
}

此控制器定義兩個符合 URL 路徑 /Products/Edit/17 和路由資料 { controller = Products, action = Edit, id = 17 } 的動作。This controller defines two actions that would match the URL path /Products/Edit/17 and route data { controller = Products, action = Edit, id = 17 }. 這是 MVC 控制器的典型模式,其中 Edit(int) 會顯示用以編輯產品的表單,而 Edit(int, Product) 會處理已張貼的表單。This is a typical pattern for MVC controllers where Edit(int) shows a form to edit a product, and Edit(int, Product) processes the posted form. 若要執行這項操作,MVC 必須在要求為 HTTP POST 時選擇 Edit(int, Product),並在 HTTP 動詞命令為任何其他項目時選擇 Edit(int)To make this possible MVC would need to choose Edit(int, Product) when the request is an HTTP POST and Edit(int) when the HTTP verb is anything else.

HttpPostAttribute ( [HttpPost] ) 是 IActionConstraint 的實作,只有在 HTTP 動詞命令為 POST 時,才能選取此動作。The HttpPostAttribute ( [HttpPost] ) is an implementation of IActionConstraint that will only allow the action to be selected when the HTTP verb is POST. IActionConstraint 的存在使 Edit(int, Product)Edit(int)「更符合」,因此會先嘗試 Edit(int, Product)The presence of an IActionConstraint makes the Edit(int, Product) a 'better' match than Edit(int), so Edit(int, Product) will be tried first.

您只需要在特殊情況下撰寫自訂 IActionConstraint 實作,但請務必了解 HttpPostAttribute 等屬性的角色 (其他 HTTP 動詞命令會定義類似的屬性)。You will only need to write custom IActionConstraint implementations in specialized scenarios, but it's important to understand the role of attributes like HttpPostAttribute - similar attributes are defined for other HTTP verbs. 在慣例路由中,當動作是 show form -> submit form 工作流程的一部分時,通常會使用相同的動作名稱。In conventional routing it's common for actions to use the same action name when they're part of a show form -> submit form workflow. 檢閱了解 IActionConstraint 一節之後,會更清楚此模式的便利性。The convenience of this pattern will become more apparent after reviewing the Understanding IActionConstraint section.

如果多個路由相符,而且 MVC 找不到「最佳」路由,則會擲回 AmbiguousActionExceptionIf multiple routes match, and MVC can't find a 'best' route, it will throw an AmbiguousActionException.

路由名稱Route names

下列範例中的字串 "blog""default" 是路由名稱:The strings "blog" and "default" in the following examples are route names:

app.UseMvc(routes =>
{
   routes.MapRoute("blog", "blog/{*article}",
               defaults: new { controller = "Blog", action = "Article" });
   routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

路由名稱為路由提供邏輯名稱,因此可以使用具名路由來產生 URL。The route names give the route a logical name so that the named route can be used for URL generation. 當路由排序可能使 URL 產生作業變得複雜時,這樣做可大幅簡化 URL 產生作業。This greatly simplifies URL creation when the ordering of routes could make URL generation complicated. 在整個應用程式內路由名稱必須是唯一的。Route names must be unique application-wide.

路由名稱不會影響要求的 URL 比對或處理,只會用於 URL 產生。Route names have no impact on URL matching or handling of requests; they're used only for URL generation. 路由提供 URL 產生的詳細資訊,包括 MVC 特定協助程式中的 URL 產生。Routing has more detailed information on URL generation including URL generation in MVC-specific helpers.

屬性路由Attribute routing

屬性路由使用一組屬性,將動作直接對應至路由範本。Attribute routing uses a set of attributes to map actions directly to route templates. 在下列範例中,在 Configure 方法中使用 app.UseMvc();,且未傳遞任何路由。In the following example, app.UseMvc(); is used in the Configure method and no route is passed. HomeController 會比對一組 類似於預設路由 {controller=Home}/{action=Index}/{id?} 所比對的 URL:The HomeController will match a set of URLs similar to what the default route {controller=Home}/{action=Index}/{id?} would match:

public class HomeController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   public IActionResult Index()
   {
      return View();
   }
   [Route("Home/About")]
   public IActionResult About()
   {
      return View();
   }
   [Route("Home/Contact")]
   public IActionResult Contact()
   {
      return View();
   }
}

HomeController.Index() 動作會針對 URL 路徑 //Home/Home/Index 的任何一個來執行。The HomeController.Index() action will be executed for any of the URL paths /, /Home, or /Home/Index.

注意

此範例醒目提示屬性路由與慣例路由之間的主要程式設計差異。This example highlights a key programming difference between attribute routing and conventional routing. 屬性路由需要更多輸入才能指定路由,而慣例預設路由處理路由的方式則更簡潔。Attribute routing requires more input to specify a route; the conventional default route handles routes more succinctly. 不過,屬性路由允許 (並需要) 精確地控制套用至每個動作的路由範本。However, attribute routing allows (and requires) precise control of which route templates apply to each action.

使用屬性路由,選取動作時「不會」根據控制器名稱和動作名稱。With attribute routing the controller name and action names play no role in which action is selected. 此範例會符合與上一個範例相同的 URL。This example will match the same URLs as the previous example.

public class MyDemoController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   public IActionResult MyIndex()
   {
      return View("Index");
   }
   [Route("Home/About")]
   public IActionResult MyAbout()
   {
      return View("About");
   }
   [Route("Home/Contact")]
   public IActionResult MyContact()
   {
      return View("Contact");
   }
}

注意

上述路由範本未定義 actionareacontroller 的路由參數。The route templates above don't define route parameters for action, area, and controller. 事實上,屬性路由中不允許有這些路由參數。In fact, these route parameters are not allowed in attribute routes. 由於路由範本已與一個動作建立關聯,因此剖析 URL 中的動作名稱並沒有任何意義。Since the route template is already associated with an action, it wouldn't make sense to parse the action name from the URL.

使用 Http[Verb] 屬性的屬性路由Attribute routing with Http[Verb] attributes

屬性路由也可以使用 Http[Verb] 屬性,例如 HttpPostAttributeAttribute routing can also make use of the Http[Verb] attributes such as HttpPostAttribute. 這些屬性都會接受路由範本。All of these attributes can accept a route template. 此範例顯示兩個符合相同路由範本的動作:This example shows two actions that match the same route template:

[HttpGet("/products")]
public IActionResult ListProducts()
{
   // ...
}

[HttpPost("/products")]
public IActionResult CreateProduct(...)
{
   // ...
}

針對 /products 等 URL 路徑,當 HTTP 動詞命令為 GET 時,會執行 ProductsApi.ListProducts;當 HTTP 動詞命令為 POST 時,會執行 ProductsApi.CreateProductFor a URL path like /products the ProductsApi.ListProducts action will be executed when the HTTP verb is GET and ProductsApi.CreateProduct will be executed when the HTTP verb is POST. 屬性路由會先根據路由屬性所定義的一組路由範本來比對 URL。Attribute routing first matches the URL against the set of route templates defined by route attributes. 一旦有路由範本相符,就會套用 IActionConstraint 條件約束以決定可執行的動作。Once a route template matches, IActionConstraint constraints are applied to determine which actions can be executed.

提示

建置 REST API 時,很少會想要在動作方法上使用 [Route(...)]When building a REST API, it's rare that you will want to use [Route(...)] on an action method. 最好是使用更明確的 Http*Verb*Attributes,以精確地指定 API 的支援項目。It's better to use the more specific Http*Verb*Attributes to be precise about what your API supports. REST API 的用戶端必須知道哪些路徑和 HTTP 動詞命令對應至特定邏輯作業。Clients of REST APIs are expected to know what paths and HTTP verbs map to specific logical operations.

由於屬性路由會套用至特定動作,因此輕鬆就能將參數設為路由範本定義的必要部分。Since an attribute route applies to a specific action, it's easy to make parameters required as part of the route template definition. 在此範例中,id 是 URL 路徑的必要部分。In this example, id is required as part of the URL path.

public class ProductsApiController : Controller
{
   [HttpGet("/products/{id}", Name = "Products_List")]
   public IActionResult GetProduct(int id) { ... }
}

ProductsApi.GetProduct(int) 動作會針對 /products/3 等 URL 路徑來執行,但不會針對 /products 等 URL 路徑來執行。The ProductsApi.GetProduct(int) action will be executed for a URL path like /products/3 but not for a URL path like /products. 如需路由範本和相關選項的完整描述,請參閱路由See Routing for a full description of route templates and related options.

路由名稱Route Name

下列程式碼會定義 Products_List 的「路由名稱」:The following code defines a route name of Products_List:

public class ProductsApiController : Controller
{
   [HttpGet("/products/{id}", Name = "Products_List")]
   public IActionResult GetProduct(int id) { ... }
}

您可以使用路由名稱根據特定路由來產生 URL。Route names can be used to generate a URL based on a specific route. 路由名稱不會影響路由的 URL 比對行為,只會用於 URL 產生。Route names have no impact on the URL matching behavior of routing and are only used for URL generation. 在整個應用程式內路由名稱必須是唯一的。Route names must be unique application-wide.

注意

慣例「預設路由」與此相反,只會將 id 參數定義為選擇性 ({id?})。Contrast this with the conventional default route, which defines the id parameter as optional ({id?}). 能夠精確指定 API 有些優點,像是允許將 /products/products/5 分派至不同的動作。This ability to precisely specify APIs has advantages, such as allowing /products and /products/5 to be dispatched to different actions.

合併路由Combining routes

為了避免屬性路由過於重複,控制器上的路由屬性可與個別動作上的路由屬性合併。To make attribute routing less repetitive, route attributes on the controller are combined with route attributes on the individual actions. 控制器上定義的任何路由範本都會附加到動作上的路由範本之前。Any route templates defined on the controller are prepended to route templates on the actions. 將路由屬性放在控制器上,即可讓控制器中的所有動作使用屬性路由。Placing a route attribute on the controller makes all actions in the controller use attribute routing.

[Route("products")]
public class ProductsApiController : Controller
{
   [HttpGet]
   public IActionResult ListProducts() { ... }

   [HttpGet("{id}")]
   public ActionResult GetProduct(int id) { ... }
}

在此範例中,URL 路徑 /products 可能符合 ProductsApi.ListProducts,而 URL 路徑 /products/5 可能符合 ProductsApi.GetProduct(int)In this example the URL path /products can match ProductsApi.ListProducts, and the URL path /products/5 can match ProductsApi.GetProduct(int). 由於這兩種動作是以 HttpGetAttribute 裝飾,因此只會符合 HTTP GETBoth of these actions only match HTTP GET because they're decorated with the HttpGetAttribute.

套用至開頭為 /~/ 之動作的路由範本,無法與套用至控制器的路由範本合併。Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller. 此範例會比對一組類似於「預設路由」的 URL 路徑。This example matches a set of URL paths similar to the default route.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]      // Combines to define the route template "Home"
    [Route("Index")] // Combines to define the route template "Home/Index"
    [Route("/")]     // Doesn't combine, defines the route template ""
    public IActionResult Index()
    {
        ViewData["Message"] = "Home index";
        var url = Url.Action("Index", "Home");
        ViewData["Message"] = "Home index" + "var url = Url.Action; =  " + url;
        return View();
    }

    [Route("About")] // Combines to define the route template "Home/About"
    public IActionResult About()
    {
        return View();
    }   
}

排序屬性路由Ordering attribute routes

相較於依已定義順序執行的慣例路由,屬性路由會建立樹狀並同時比對所有路由。In contrast to conventional routes which execute in a defined order, attribute routing builds a tree and matches all routes simultaneously. 此行為如同依理想的排序來排列路由項目,最明確的路由會有機會在較泛型的路由之前執行。This behaves as-if the route entries were placed in an ideal ordering; the most specific routes have a chance to execute before the more general routes.

例如,blog/search/{topic} 等路由比 blog/{*article} 等路由更明確。For example, a route like blog/search/{topic} is more specific than a route like blog/{*article}. 邏輯上來說,預設會先「執行」blog/search/{topic},因為這是唯一合理的排序。Logically speaking the blog/search/{topic} route 'runs' first, by default, because that's the only sensible ordering. 使用慣例路由,開發人員會負責依想要的順序來排列路由。Using conventional routing, the developer is responsible for placing routes in the desired order.

您可以使用架構提供之所有路由屬性的 Order 屬性,來設定屬性路由的順序。Attribute routes can configure an order, using the Order property of all of the framework provided route attributes. 路由會依 Order 屬性的遞增排序來處理。Routes are processed according to an ascending sort of the Order property. 預設順序為 0The default order is 0. 使用 Order = -1 設定的路由會在未設定順序的路由之前執行。Setting a route using Order = -1 will run before routes that don't set an order. 使用 Order = 1 設定的路由會在預設路由排序之後執行。Setting a route using Order = 1 will run after default route ordering.

提示

請避免依賴 OrderAvoid depending on Order. 如果您的 URL 空間需要明確的順序值才能正確地路由,則同樣也可能會使用戶端混淆。If your URL-space requires explicit order values to route correctly, then it's likely confusing to clients as well. 一般而言,屬性路由會透過 URL 比對來選取正確的路由。In general attribute routing will select the correct route with URL matching. 如果用於 URL 產生的預設順序無效,使用路由名稱作為覆寫通常會比套用 Order 屬性更簡單。If the default order used for URL generation isn't working, using route name as an override is usually simpler than applying the Order property.

Razor Pages 路由和 MVC 控制器路由會共用實作。Razor Pages routing and MVC controller routing share an implementation. 如需 Razor Pages 主題中路由順序的資訊,請參閱 Razor Pages 路由和應用程式慣例:路由順序Information on route order in the Razor Pages topics is available at Razor Pages route and app conventions: Route order.

路由範本中的語彙基元取代 ([controller]、[action]、[area])Token replacement in route templates ([controller], [action], [area])

為了方便起見,屬性路由支援以方括號 ([]) 括住語彙基元的「語彙基元取代」。For convenience, attribute routes support token replacement by enclosing a token in square-braces ([, ]). 語彙基元 [action][area][controller] 會分別以定義路由之動作的動作名稱值、區域名稱值和控制器名稱值來取代。The tokens [action], [area], and [controller] are replaced with the values of the action name, area name, and controller name from the action where the route is defined. 在下列範例中,動作會符合註解中所述的 URL 路徑:In the following example, the actions match URL paths as described in the comments:

[Route("[controller]/[action]")]
public class ProductsController : Controller
{
    [HttpGet] // Matches '/Products/List'
    public IActionResult List() {
        // ...
    }

    [HttpGet("{id}")] // Matches '/Products/Edit/{id}'
    public IActionResult Edit(int id) {
        // ...
    }
}

語彙基元取代會在建立屬性路由的最後一個步驟發生。Token replacement occurs as the last step of building the attribute routes. 上述範例的運作方式與下列程式碼相同:The above example will behave the same as the following code:


public class ProductsController : Controller
{
    [HttpGet("[controller]/[action]")] // Matches '/Products/List'
    public IActionResult List() {
        // ...
    }

    [HttpGet("[controller]/[action]/{id}")] // Matches '/Products/Edit/{id}'
    public IActionResult Edit(int id) {
        // ...
    }
}

屬性路由也可以透過繼承來合併。Attribute routes can also be combined with inheritance. 這與語彙基元取代結合會特別強大。This is particularly powerful combined with token replacement.

[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }

public class ProductsController : MyBaseController
{
   [HttpGet] // Matches '/api/Products'
   public IActionResult List() { ... }

   [HttpPut("{id}")] // Matches '/api/Products/{id}'
   public IActionResult Edit(int id) { ... }
}

語彙基元取代也適用於屬性路由所定義的路由名稱。Token replacement also applies to route names defined by attribute routes. [Route("[controller]/[action]", Name="[controller]_[action]")] 會針對每個動作產生唯一的路由名稱。[Route("[controller]/[action]", Name="[controller]_[action]")] generates a unique route name for each action.

若要比對常值語彙基元取代分隔符號 [],請重複字元 ([[]]) 來將它逸出。To match the literal token replacement delimiter [ or ], escape it by repeating the character ([[ or ]]).

使用參數轉換程式自訂語彙基元取代Use a parameter transformer to customize token replacement

可以使用參數轉換程式自訂語彙基元取代。Token replacement can be customized using a parameter transformer. 參數轉換程式會實作 IOutboundParameterTransformer 並轉換參數值。A parameter transformer implements IOutboundParameterTransformer and transforms the value of parameters. 例如,自訂 SlugifyParameterTransformer 參數轉換器會將 SubscriptionManagement 路由值變更為 subscription-managementFor example, a custom SlugifyParameterTransformer parameter transformer changes the SubscriptionManagement route value to subscription-management.

RouteTokenTransformerConvention 是一個應用程式模型慣例,它會:The RouteTokenTransformerConvention is an application model convention that:

  • 將參數轉換程式套用到應用程式中的所有屬性路由。Applies a parameter transformer to all attribute routes in an application.
  • 在取代屬性路由語彙基元值時自訂它們。Customizes the attribute route token values as they are replaced.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")] // Matches '/subscription-management/list-all'
    public IActionResult ListAll() { ... }
}

RouteTokenTransformerConvention 會在 ConfigureServices 中註冊為選項。The RouteTokenTransformerConvention is registered as an option in ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        if (value == null) { return null; }

        // Slugify value
        return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
    }
}

多個路由Multiple Routes

屬性路由支援定義多個路由來達到相同的動作。Attribute routing supports defining multiple routes that reach the same action. 最常見的用法是模擬「預設慣例路由」的行為,如下列範例所示:The most common usage of this is to mimic the behavior of the default conventional route as shown in the following example:

[Route("[controller]")]
public class ProductsController : Controller
{
   [Route("")]     // Matches 'Products'
   [Route("Index")] // Matches 'Products/Index'
   public IActionResult Index()
}

將多個路由屬性放在控制器上,表示這些屬性會各自與動作方法上的每個路由屬性合併。Putting multiple route attributes on the controller means that each one will combine with each of the route attributes on the action methods.

[Route("Store")]
[Route("[controller]")]
public class ProductsController : Controller
{
   [HttpPost("Buy")]     // Matches 'Products/Buy' and 'Store/Buy'
   [HttpPost("Checkout")] // Matches 'Products/Checkout' and 'Store/Checkout'
   public IActionResult Buy()
}

當一個動作上放有多個路由屬性 (實作IActionConstraint) 時,則每個動作條件約束都會與屬性中用來定義的路由範本合併。When multiple route attributes (that implement IActionConstraint) are placed on an action, then each action constraint combines with the route template from the attribute that defined it.

[Route("api/[controller]")]
public class ProductsController : Controller
{
   [HttpPut("Buy")]      // Matches PUT 'api/Products/Buy'
   [HttpPost("Checkout")] // Matches POST 'api/Products/Checkout'
   public IActionResult Buy()
}

提示

雖然在動作上使用多個路由看似強大,但最好保持簡單且妥善定義的應用程式 URL 空間。While using multiple routes on actions can seem powerful, it's better to keep your application's URL space simple and well-defined. 只有必要時,才在動作上使用多個路由;例如,為了支援現有的用戶端。Use multiple routes on actions only where needed, for example to support existing clients.

指定屬性路由的選擇性參數、預設值和條件約束Specifying attribute route optional parameters, default values, and constraints

屬性路由支援使用與慣例路由相同的內嵌語法,來指定選擇性參數、預設值和條件約束。Attribute routes support the same inline syntax as conventional routes to specify optional parameters, default values, and constraints.

[HttpPost("product/{id:int}")]
public IActionResult ShowProduct(int id)
{
   // ...
}

如需路由範本語法的詳細描述,請參閱路由範本參考See Route Template Reference for a detailed description of route template syntax.

使用 IRouteTemplateProvider 的自訂路由屬性Custom route attributes using IRouteTemplateProvider

架構中提供的所有路由屬性 ([Route(...)][HttpGet(...)] 等) 都會實作 IRouteTemplateProvider 介面。All of the route attributes provided in the framework ( [Route(...)], [HttpGet(...)] , etc.) implement the IRouteTemplateProvider interface. 當應用程式啟動並使用實作 IRouteTemplateProvider 的屬性來建立初始路由集時,MVC 會尋找控制器類別和動作方法上的屬性。MVC looks for attributes on controller classes and action methods when the app starts and uses the ones that implement IRouteTemplateProvider to build the initial set of routes.

您可以實作 IRouteTemplateProvider 來定義自己的路由屬性。You can implement IRouteTemplateProvider to define your own route attributes. 每個 IRouteTemplateProvider 都可讓您定義具有自訂路由範本、順序和名稱的單一路由:Each IRouteTemplateProvider allows you to define a single route with a custom route template, order, and name:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
   public string Template => "api/[controller]";

   public int? Order { get; set; }

   public string Name { get; set; }
}

上述範例中的屬性會在套用 [MyApiController] 時,自動將 Template 設定為 "api/[controller]"The attribute from the above example automatically sets the Template to "api/[controller]" when [MyApiController] is applied.

使用應用程式模型自訂屬性路由Using Application Model to customize attribute routes

「應用程式模型」是在啟動時建立的物件模型,其中包含 MVC 用來路由及執行動作的所有中繼資料。The application model is an object model created at startup with all of the metadata used by MVC to route and execute your actions. 「應用程式模型」包含從路由屬性收集的所有資料 (透過 IRouteTemplateProvider)。The application model includes all of the data gathered from route attributes (through IRouteTemplateProvider). 您可以撰寫「慣例」來修改啟動時的應用程式模型,以自訂路由的運作方式。You can write conventions to modify the application model at startup time to customize how routing behaves. 本節簡單示範如何使用應用程式模型來自訂路由。This section shows a simple example of customizing routing using application model.

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;
using System.Text;
public class NamespaceRoutingConvention : 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)
        {
            // This controller manually defined some routes, so treat this 
            // as an override and not apply the convention here.
            return;
        }

        // Use the namespace and controller name to infer a route for the controller.
        //
        // Example:
        //
        //  controller.ControllerTypeInfo ->    "My.Application.Admin.UsersController"
        //  baseNamespace ->                    "My.Application"
        //
        //  template =>                         "Admin/[controller]"
        //
        // This makes your routes roughly line up with the folder structure of your project.
        //
        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]");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

混合路由:屬性路由與慣例路由Mixed routing: Attribute routing vs conventional routing

MVC 應用程式可以混用慣例路由與屬性路由。MVC applications can mix the use of conventional routing and attribute routing. 控制器通常會使用慣例路由來提供 HTML 頁面給瀏覽器,並使用屬性路由來提供 REST API。It's typical to use conventional routes for controllers serving HTML pages for browsers, and attribute routing for controllers serving REST APIs.

動作可以使用慣例路由或屬性路由。Actions are either conventionally routed or attribute routed. 將路由放在控制器或動作上,即可讓它使用屬性路由。Placing a route on the controller or the action makes it attribute routed. 定義屬性路由的動作無法透過慣例路由到達,反之亦然。Actions that define attribute routes cannot be reached through the conventional routes and vice-versa. 控制器上的任何路由屬性可讓控制器中的所有動作使用屬性路由。Any route attribute on the controller makes all actions in the controller attribute routed.

注意

這兩種路由系統的區別在於 URL 符合某個路由範本之後所套用的程序。What distinguishes the two types of routing systems is the process applied after a URL matches a route template. 在慣例路由中,會使用相符項目中的路由值,從所有慣例路由動作的查閱資料表中選擇動作和控制器。In conventional routing, the route values from the match are used to choose the action and controller from a lookup table of all conventional routed actions. 在屬性路由中,每個範本已與一個動作建立關聯,因此不需要進一步查閱。In attribute routing, each template is already associated with an action, and no further lookup is needed.

複雜區段Complex segments

複雜區段 (例如,[Route("/dog{token}cat")]) 會透過以非窮盡的方式,由右至左比對常值來處理。Complex segments (for example, [Route("/dog{token}cat")]), are processed by matching up literals from right to left in a non-greedy way. 如需說明,請參閱原始程式碼See the source code for a description. 如需詳細資訊,請參閱此問題For more information, see this issue.

URL 產生URL Generation

MVC 應用程式可以使用路由的 URL 產生功能,來產生動作的 URL 連結。MVC applications can use routing's URL generation features to generate URL links to actions. 產生 URL 可排除硬式編碼的 URL,讓程式碼更穩定且更容易維護。Generating URLs eliminates hardcoding URLs, making your code more robust and maintainable. 本節著重於 MVC 所提供的 URL 產生功能,並只會涵蓋 URL 產生運作方式的基本概念。This section focuses on the URL generation features provided by MVC and will only cover basics of how URL generation works. 如需 URL 產生的詳細描述,請參閱路由See Routing for a detailed description of URL generation.

IUrlHelper 介面是 MVC 與用於產生 URL 的路由之間的基礎結構部分。The IUrlHelper interface is the underlying piece of infrastructure between MVC and routing for URL generation. 您將會透過控制器、檢視和檢視元件中 Url 屬性,來尋找可用的 IUrlHelper 執行個體。You'll find an instance of IUrlHelper available through the Url property in controllers, views, and view components.

在此範例中,會透過 Controller.Url 屬性使用 IUrlHelper 介面來產生另一個動作的 URL。In this example, the IUrlHelper interface is used through the Controller.Url property to generate a URL to another action.

using Microsoft.AspNetCore.Mvc;

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return Content($"Go check out {url}, it's really great.");
    }

    public IActionResult Destination()
    {
        return View();
    }
}

如果應用程式使用預設慣例路由,url 變數的值會是 URL 路徑字串 /UrlGeneration/DestinationIf the application is using the default conventional route, the value of the url variable will be the URL path string /UrlGeneration/Destination. 此 URL 路徑是透過路由所建立,結合了目前要求中的路由值 (環境值) 與傳遞至 Url.Action 的值,並在路由範本中取代這些值:This URL path is created by routing by combining the route values from the current request (ambient values), with the values passed to Url.Action and substituting those values into the route template:

ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

路由範本中每個路由參數的值都會以相符名稱的值和環境值所取代。Each route parameter in the route template has its value substituted by matching names with the values and ambient values. 不具有任何值的路由參數可以使用預設值 (若有);如果是選擇性,則可以略過 (如此範例中的 id 所示)。A route parameter that doesn't have a value can use a default value if it has one, or be skipped if it's optional (as in the case of id in this example). 如果任何必要的路由參數沒有對應的值,URL 產生會失敗。URL generation will fail if any required route parameter doesn't have a corresponding value. 如果某個路由的 URL 產生失敗,則會嘗試下一個路由,直到嘗試所有路由或找到相符項目為止。If URL generation fails for a route, the next route is tried until all routes have been tried or a match is found.

上述 Url.Action 範例假設使用慣例路由,但 URL 產生的運作方式會類似屬性路由,雖然概念完全不同。The example of Url.Action above assumes conventional routing, but URL generation works similarly with attribute routing, though the concepts are different. 在慣例路由中,使用路由值來展開範本,而且 controlleraction 的路由值通常會出現在該範本中 (這是因為路由比對的 URL 符合「慣例」)。With conventional routing, the route values are used to expand a template, and the route values for controller and action usually appear in that template - this works because the URLs matched by routing adhere to a convention. 在屬性路由中,controlleraction 的路由值不可以出現在範本中,而是用來查閱要使用的範本。In attribute routing, the route values for controller and action are not allowed to appear in the template - they're instead used to look up which template to use.

此範例使用屬性路由:This example uses attribute routing:

// In Startup class
public void Configure(IApplicationBuilder app)
{
    app.UseMvc();
}
using Microsoft.AspNetCore.Mvc;

public class UrlGenerationController : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination"); // Generates /custom/url/to/destination
        return Content($"Go check out {url}, it's really great.");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination() {
        return View();
    }
}

MVC 建立所有屬性路由動作的查閱資料表,並將比對 controlleraction 值,以選取要用於 URL 產生的路由範本。MVC builds a lookup table of all attribute routed actions and will match the controller and action values to select the route template to use for URL generation. 在上述範例中,會產生 custom/url/to/destinationIn the sample above, custom/url/to/destination is generated.

由動作名稱產生 URLGenerating URLs by action name

Url.Action (IUrlHelper .Url.Action (IUrlHelper . Action) 及所有相關多載的基本概念,都是您想要透過指定控制器名稱和動作名稱,來指定連結的項目。Action) and all related overloads all are based on that idea that you want to specify what you're linking to by specifying a controller name and action name.

注意

使用 Url.Action 時,會為您指定controlleraction 的目前路由值 (controlleraction 的值都屬於「環境值」和「值」)。When using Url.Action, the current route values for controller and action are specified for you - the value of controller and action are part of both ambient values and values. 方法 Url.Action 一律會使用 actioncontroller 的目前值,而且會產生路由至目前動作的 URL 路徑。The method Url.Action, always uses the current values of action and controller and will generate a URL path that routes to the current action.

路由會嘗試使用環境值中的值,來填入您在產生 URL 時未提供的資訊。Routing attempts to use the values in ambient values to fill in information that you didn't provide when generating a URL. 使用 {a}/{b}/{c}/{d} 等路由和環境值 { a = Alice, b = Bob, c = Carol, d = David },路由就會有足夠的資訊來產生不含任何其他值的 URL (因為所有路由參數都具有值)。Using a route like {a}/{b}/{c}/{d} and ambient values { a = Alice, b = Bob, c = Carol, d = David }, routing has enough information to generate a URL without any additional values - since all route parameters have a value. 如果您新增值 { d = Donovan },則會忽略值 { d = David },而且產生的 URL 路徑會是 Alice/Bob/Carol/DonovanIf you added the value { d = Donovan }, the value { d = David } would be ignored, and the generated URL path would be Alice/Bob/Carol/Donovan.

警告

URL 路徑是階層式。URL paths are hierarchical. 在上述範例中,如果您新增了值 { c = Cheryl },則會忽略 { c = Carol, d = David } 這兩個值。In the example above, if you added the value { c = Cheryl }, both of the values { c = Carol, d = David } would be ignored. 在此情況下,不再有 d 的值,因此 URL 產生會失敗。In this case we no longer have a value for d and URL generation will fail. 您必須指定 cd 所需的值。You would need to specify the desired value of c and d. 使用預設路由 ({controller}/{action}/{id?}) 可能會遇到此問題,但實際上很少會遇到此行為,因為 Url.Action 一律會明確指定 controlleraction 值。You might expect to hit this problem with the default route ({controller}/{action}/{id?}) - but you will rarely encounter this behavior in practice as Url.Action will always explicitly specify a controller and action value.

較長的 Url.Action 多載還會接受一個額外的「路由值」物件,來為 controlleraction 以外的路由參數提供值。Longer overloads of Url.Action also take an additional route values object to provide values for route parameters other than controller and action. 您最常會在搭配 id 時看到此用法,例如 Url.Action("Buy", "Products", new { id = 17 })You will most commonly see this used with id like Url.Action("Buy", "Products", new { id = 17 }). 依照慣例,「路由值」物件通常是匿名型別的物件,但也可以是 IDictionary<> 或「純舊 .NET 物件」。By convention the route values object is usually an object of anonymous type, but it can also be an IDictionary<> or a plain old .NET object. 不符合路由參數的任何額外路由值都會放在查詢字串中。Any additional route values that don't match route parameters are put in the query string.

using Microsoft.AspNetCore.Mvc;

public class TestController : Controller
{
    public IActionResult Index()
    {
        // Generates /Products/Buy/17?color=red
        var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
        return Content(url);
    }
}

提示

若要建立絕對 URL,請使用接受 protocol 的多載:Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme)To create an absolute URL, use an overload that accepts a protocol: Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme)

由路由產生 URLGenerating URLs by route

上述程式碼示範如何藉由傳入控制器和動作名稱來產生 URL。The code above demonstrated generating a URL by passing in the controller and action name. IUrlHelper 也提供 Url.RouteUrl 系列方法。IUrlHelper also provides the Url.RouteUrl family of methods. 這些方法類似於 Url.Action,但不會將 actioncontroller 的目前值複製到路由值。These methods are similar to Url.Action, but they don't copy the current values of action and controller to the route values. 最常見的用法是指定路由名稱使用特定路由來產生 URL,通常「不需要」指定控制器或動作名稱。The most common usage is to specify a route name to use a specific route to generate the URL, generally without specifying a controller or action name.

using Microsoft.AspNetCore.Mvc;

public class UrlGenerationController : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route"); // Generates /custom/url/to/destination
        return Content($"See {url}, it's really great.");
    }

    [HttpGet("custom/url/to/destination", Name = "Destination_Route")]
    public IActionResult Destination() {
        return View();
    }
}

在 HTML 中產生 URLGenerating URLs in HTML

IHtmlHelper 提供 HtmlHelper 方法 Html.BeginFormHtml.ActionLink,以分別產生 <form><a> 項目。IHtmlHelper provides the HtmlHelper methods Html.BeginForm and Html.ActionLink to generate <form> and <a> elements respectively. 這些方法使用 Url.Action 方法來產生 URL,並接受類似的引數。These methods use the Url.Action method to generate a URL and they accept similar arguments. HtmlHelper 的成對 Url.RouteUrlHtml.BeginRouteFormHtml.RouteLink,這兩者的功能很類似。The Url.RouteUrl companions for HtmlHelper are Html.BeginRouteForm and Html.RouteLink which have similar functionality.

TagHelper 透過 form TagHelper 和 <a> TagHelper 產生 URL。TagHelpers generate URLs through the form TagHelper and the <a> TagHelper. 這兩者使用 IUrlHelper 進行實作。Both of these use IUrlHelper for their implementation. 如需詳細資訊,請參閱使用表單See Working with Forms for more information.

在檢視中,可透過 Url 屬性使用 IUrlHelper 來產生上述未涵蓋的任何特定 URL。Inside views, the IUrlHelper is available through the Url property for any ad-hoc URL generation not covered by the above.

在動作結果中產生 URLGenerating URLS in Action Results

上述範例示範如何在控制器中使用 IUrlHelper,但控制器中最常見的用法是產生 URL 以作為動作結果的一部分。The examples above have shown using IUrlHelper in a controller, while the most common usage in a controller is to generate a URL as part of an action result.

ControllerBaseController 基底類別提供便利的方法讓動作結果可參考其他動作。The ControllerBase and Controller base classes provide convenience methods for action results that reference another action. 一個典型的用法是在接受使用者輸入之後重新導向。One typical usage is to redirect after accepting user input.

public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        return RedirectToAction("Index");
    }
    return View(customer);
}

動作結果的 Factory 方法遵循類似於 IUrlHelper 上方法的模式。The action results factory methods follow a similar pattern to the methods on IUrlHelper.

專用慣例路由的特殊案例Special case for dedicated conventional routes

慣例路由可使用一種特殊路由定義,稱為「專用慣例路由」。Conventional routing can use a special kind of route definition called a dedicated conventional route. 在下列範例中,名為 blog 的路由是專用慣例路由。In the example below, the route named blog is a dedicated conventional route.

app.UseMvc(routes =>
{
    routes.MapRoute("blog", "blog/{*article}",
        defaults: new { controller = "Blog", action = "Article" });
    routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

透過這些路由定義,Url.Action("Index", "Home") 會使用 default 路由產生 URL 路徑 /,但為什麼?Using these route definitions, Url.Action("Index", "Home") will generate the URL path / with the default route, but why? 您可能會猜想路由值 { controller = Home, action = Index } 便足以使用 blog 來產生 URL,且結果會是 /blog?action=Index&controller=HomeYou might guess the route values { controller = Home, action = Index } would be enough to generate a URL using blog, and the result would be /blog?action=Index&controller=Home.

專用慣例路由依賴沒有對應路由參數之預設值的特殊行為,以防止用於 URL 產生的路由變得「太窮盡」。Dedicated conventional routes rely on a special behavior of default values that don't have a corresponding route parameter that prevents the route from being "too greedy" with URL generation. 在本例中,預設值為 { controller = Blog, action = Article }controlleraction 都不會顯示為路由參數。In this case the default values are { controller = Blog, action = Article }, and neither controller nor action appears as a route parameter. 當路由執行 URL 產生時,所提供的值必須符合預設值。When routing performs URL generation, the values provided must match the default values. 使用 blog 產生 URL 會失敗,因為值 { controller = Home, action = Index } 不符合 { controller = Blog, action = Article }URL generation using blog will fail because the values { controller = Home, action = Index } don't match { controller = Blog, action = Article }. 路由會接著切換並嘗試 default,此時會成功。Routing then falls back to try default, which succeeds.

區域Areas

區域是 MVC 功能,可將相關功能組織成群組,作為個別路由命名空間 (適用於控制器動作) 和資料夾結構 (適用於檢視)。Areas are an MVC feature used to organize related functionality into a group as a separate routing-namespace (for controller actions) and folder structure (for views). 使用區域可讓應用程式具有多個同名的控制器 (只要這些控制器具有不同的「區域」即可)。Using areas allows an application to have multiple controllers with the same name - as long as they have different areas. 使用區域可建立用於路由的階層,方法是將另一個路由參數 area 新增至 controlleractionUsing areas creates a hierarchy for the purpose of routing by adding another route parameter, area to controller and action. 本節將討論路由如何與區域互動;如需區域如何與檢視搭配使用的詳細資料,請參閱區域This section will discuss how routing interacts with areas - see Areas for details about how areas are used with views.

下列範例會設定 MVC 使用預設慣例路由,並為名為 Blog 的區域設定「區域路由」:The following example configures MVC to use the default conventional route and an area route for an area named Blog:

app.UseMvc(routes =>
{
    routes.MapAreaRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});

當符合 /Manage/Users/AddUser 等 URL 路徑時,第一個路由會產生路由值 { area = Blog, controller = Users, action = AddUser }When matching a URL path like /Manage/Users/AddUser, the first route will produce the route values { area = Blog, controller = Users, action = AddUser }. area 路由值是由 area 的預設值所產生;事實上,MapAreaRoute 所建立的路由相當於:The area route value is produced by a default value for area, in fact the route created by MapAreaRoute is equivalent to the following:

app.UseMvc(routes =>
{
    routes.MapRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
    routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});

MapAreaRoute 會針對使用所提供之區域名稱 (在本例中為 Blog) 的 area,使用預設值和條件約束來建立路由。MapAreaRoute creates a route using both a default value and constraint for area using the provided area name, in this case Blog. 預設值可確保路由一律會產生 { area = Blog, ... },而條件約束需要 { area = Blog, ... } 值以產生 URL。The default value ensures that the route always produces { area = Blog, ... }, the constraint requires the value { area = Blog, ... } for URL generation.

提示

慣例路由與順序息息相關。Conventional routing is order-dependent. 一般而言,具有區域的路由應該放在路由表前面,因為這些路由比沒有區域的路由更明確。In general, routes with areas should be placed earlier in the route table as they're more specific than routes without an area.

使用上述範例,路由值會符合下列動作:Using the above example, the route values would match the following action:

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        public IActionResult AddUser()
        {
            return View();
        }        
    }
}

AreaAttribute 可用來指定控制器為區域的一部分,假設此控制器在 Blog 區域中。The AreaAttribute is what denotes a controller as part of an area, we say that this controller is in the Blog area. 不含 [Area] 屬性的控制器不是任何區域的成員,因此當路由提供 area 路由值時不會符合。Controllers without an [Area] attribute are not members of any area, and will not match when the area route value is provided by routing. 在下列範例中,只有列出的第一個控制器可能符合路由值 { area = Blog, controller = Users, action = AddUser }In the following example, only the first controller listed can match the route values { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        public IActionResult AddUser()
        {
            return View();
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        public IActionResult AddUser()
        {
            return View();
        }        
    }
}
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
    {
        public IActionResult AddUser()
        {
            return View();

        }
    }
}

注意

為求完整起見,此處顯示每個控制器的命名空間,否則控制器會有命名衝突並產生編譯器錯誤。The namespace of each controller is shown here for completeness - otherwise the controllers would have a naming conflict and generate a compiler error. 類別命名空間不會影響 MVC 的路由。Class namespaces have no effect on MVC's routing.

前兩個控制器是區域的成員,只有在 area 路由值提供其各自的區域名稱時才會符合。The first two controllers are members of areas, and only match when their respective area name is provided by the area route value. 第三個控制器不是任何區域的成員,只有路由未提供任何值給 area 時才會符合。The third controller isn't a member of any area, and can only match when no value for area is provided by routing.

注意

在「未符合任何值」的情況下,缺少 area 值相當於 area 的值為 Null 或空字串。In terms of matching no value, the absence of the area value is the same as if the value for area were null or the empty string.

執行區域中的動作時,area 的路由值會作為路由用於 URL 產生的「環境值」。When executing an action inside an area, the route value for area will be available as an ambient value for routing to use for URL generation. 這表示區域預設會以「黏性」方式來處理 URL 產生,如下列範例所示。This means that by default areas act sticky for URL generation as demonstrated by the following sample.

app.UseMvc(routes =>
{
    routes.MapAreaRoute("duck_route", "Duck",
        "Manage/{controller}/{action}/{id?}");
    routes.MapRoute("default", "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area
            var url = Url.Action("Index", "Home"); 
            // returns /Manage
            return Content(url);
        }

        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area
            var url = Url.Action("Index", "Home", new { area = "" }); 
            // returns /Manage/Home/Index
            return Content(url);
        }
    }
}

了解 IActionConstraintUnderstanding IActionConstraint

注意

本節深入探討架構內部及 MVC 如何選擇要執行的動作。This section is a deep-dive on framework internals and how MVC chooses an action to execute. 一般應用程式不需要自訂 IActionConstraintA typical application won't need a custom IActionConstraint

即使您不熟悉 IActionConstraint,也可能已使用過此介面。You have likely already used IActionConstraint even if you're not familiar with the interface. [HttpGet] 屬性和類似的 [Http-VERB] 屬性會實作 IActionConstraint 來限制動作方法的執行。The [HttpGet] Attribute and similar [Http-VERB] attributes implement IActionConstraint in order to limit the execution of an action method.

public class ProductsController : Controller
{
    [HttpGet]
    public IActionResult Edit() { }

    public IActionResult Edit(...) { }
}

假設在預設慣例路由中,URL 路徑 /Products/Edit 會產生值 { controller = Products, action = Edit },該值符合此處所示的兩個動作。Assuming the default conventional route, the URL path /Products/Edit would produce the values { controller = Products, action = Edit }, which would match both of the actions shown here. IActionConstraint 術語中,我們會假設這兩個動作可視為候選項目 (因為它們都符合路由資料)。In IActionConstraint terminology we would say that both of these actions are considered candidates - as they both match the route data.

HttpGetAttribute 執行時,Edit() 會符合 GET,但不符合任何其他 HTTP 動詞命令。When the HttpGetAttribute executes, it will say that Edit() is a match for GET and isn't a match for any other HTTP verb. Edit(...) 動作未定義任何條件約束,因此會符合任何 HTTP 動詞命令。The Edit(...) action doesn't have any constraints defined, and so will match any HTTP verb. 假設 POST - 只有 Edit(...) 相符。So assuming a POST - only Edit(...) matches. 但對於 GET,這兩個動作仍然相符;不過,具有 IActionConstraint 的動作一律比沒有的動作「更符合」。But, for a GET both actions can still match - however, an action with an IActionConstraint is always considered better than an action without. 由於 Edit() 具有 [HttpGet],因此更明確;如果兩個動作都相符,就會選取此動作。So because Edit() has [HttpGet] it's considered more specific, and will be selected if both actions can match.

就概念而言,IActionConstraint 是一種「多載」形式,但它會在符合相同 URL 的動作之間進行多載,而不是多載具有相同名稱的方法。Conceptually, IActionConstraint is a form of overloading, but instead of overloading methods with the same name, it's overloading between actions that match the same URL. 屬性路由也會使用 IActionConstraint,因此可能導致來自不同控制器的動作被視為候選項目。Attribute routing also uses IActionConstraint and can result in actions from different controllers both being considered candidates.

實作 IActionConstraintImplementing IActionConstraint

實作 IActionConstraint 的最簡單方式是建立衍生自 System.Attribute 的類別,並將它放在您的動作和控制器上。The simplest way to implement an IActionConstraint is to create a class derived from System.Attribute and place it on your actions and controllers. MVC 會自動探索作為屬性套用的任何 IActionConstraintMVC will automatically discover any IActionConstraint that are applied as attributes. 您可以使用應用程式模型來套用條件約束,由於您可以之後編寫其套用方式的程式,因此可能是最彈性的方法。You can use the application model to apply constraints, and this is probably the most flexible approach as it allows you to metaprogram how they're applied.

在下列範例中,條件約束會根據路由資料中的「國碼 (地區碼)」來選擇動作。In the following example a constraint chooses an action based on a country code from the route data. GitHub 上有完整範例The full sample on GitHub.

public class CountrySpecificAttribute : Attribute, IActionConstraint
{
    private readonly string _countryCode;

    public CountrySpecificAttribute(string countryCode)
    {
        _countryCode = countryCode;
    }

    public int Order
    {
        get
        {
            return 0;
        }
    }

    public bool Accept(ActionConstraintContext context)
    {
        return string.Equals(
            context.RouteContext.RouteData.Values["country"].ToString(),
            _countryCode,
            StringComparison.OrdinalIgnoreCase);
    }
}

您必須負責實作 Accept 方法,並選擇 'Order' 作為要執行的條件約束。You are responsible for implementing the Accept method and choosing an 'Order' for the constraint to execute. 在本例中,Accept 方法會傳回 true,表示當 country 路由值相符時動作會符合。In this case, the Accept method returns true to denote the action is a match when the country route value matches. 這與 RouteValueAttribute 不同,後者允許切換回未使用屬性的動作。This is different from a RouteValueAttribute in that it allows fallback to a non-attributed action. 此範例顯示如果您定義 en-US 動作,則 fr-FR 等國碼 (地區碼) 會切換回未套用 [CountrySpecific(...)] 的更泛型控制器。The sample shows that if you define an en-US action then a country code like fr-FR will fall back to a more generic controller that doesn't have [CountrySpecific(...)] applied.

Order 屬性決定條件約束所屬的「階段」。The Order property decides which stage the constraint is part of. 群組中的動作條件約束會根據 Order 來執行。Action constraints run in groups based on the Order. 例如,架構提供的所有 HTTP 方法屬性都會使用相同的 Order 值,以便在同一個階段中執行。For example, all of the framework provided HTTP method attributes use the same Order value so that they run in the same stage. 您可以視需要擁有許多階段來實作所需的原則。You can have as many stages as you need to implement your desired policies.

提示

若要決定 Order 的值,請考慮是否應該在 HTTP 方法之前套用條件約束。To decide on a value for Order think about whether or not your constraint should be applied before HTTP methods. 數值愈低會愈先執行。Lower numbers run first.