在 ASP.NET Core 中路由到控制器操作Routing to controller actions in ASP.NET Core

作者:Ryan NowakKirk LarkinnRick AndersonBy Ryan Nowak, Kirk Larkin, and Rick Anderson

ASP.NET Core 控制器使用路由中间件来匹配传入请求的 url,并将其映射到操作ASP.NET Core controllers use the Routing middleware to match the URLs of incoming requests and map them to actions. 路由模板:Routes templates:

  • 在启动代码或属性中定义。Are defined in startup code or attributes.
  • 描述 URL 路径如何与操作匹配。Describe how URL paths are matched to actions.
  • 用于生成链接的 Url。Are used to generate URLs for links. 生成的链接通常在响应中返回。The generated links are typically returned in responses.

操作是按逆路由或按属性路由Actions are either conventionally-routed or attribute-routed. 在控制器或操作上放置路由,使其属性路由。Placing a route on the controller or action makes it attribute-routed. 有关详细信息,请参阅混合路由See Mixed routing for more information.

本文档:This document:

  • 说明 MVC 与路由之间的交互:Explains the interactions between MVC and routing:
    • 典型的 MVC 应用使用路由功能的方式。How typical MVC apps make use of routing features.
    • 涵盖两种:Covers both:
    • 有关高级路由的详细信息,请参阅路由See Routing for advanced routing details.
  • 指 ASP.NET Core 3.0 中添加的默认路由系统,称为 "终结点路由"。Refers to the default routing system added in ASP.NET Core 3.0, called endpoint routing. 出于兼容性目的,可以将控制器用于以前版本的路由。It's possible to use controllers with the previous version of routing for compatibility purposes. 有关说明,请参阅2.2-3.0 迁移指南See the 2.2-3.0 migration guide for instructions. 有关旧路由系统上的参考材料,请参阅本文档的2.2 版本Refer to the 2.2 version of this document for reference material on the legacy routing system.

设置传统路由Set up conventional route

Startup.Configure使用传统路由时,通常具有如下所示的代码:Startup.Configure typically has code similar to the following when using conventional routing:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

在对的调用中 UseEndpointsMapControllerRoute 用于创建单个路由。Inside the call to UseEndpoints, MapControllerRoute is used to create a single route. 单路由命名为 default route。The single route is named default route. 大多数具有控制器和视图的应用都使用类似于路由的路由模板 defaultMost apps with controllers and views use a route template similar to the default route. REST Api 应使用属性路由REST APIs should use attribute routing.

路由模板 "{controller=Home}/{action=Index}/{id?}"The route template "{controller=Home}/{action=Index}/{id?}":

  • 匹配 URL 路径,例如/Products/Details/5Matches a URL path like /Products/Details/5

  • { controller = Products, action = Details, id = 5 }通过词汇切分路径来提取路由值。Extracts the route values { controller = Products, action = Details, id = 5 } by tokenizing the path. 如果应用有一个名为的控制器 ProductsController 和一个操作,则提取路由值将导致匹配 DetailsThe extraction of route values results in a match if the app has a controller named ProductsController and a Details action:

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。MyDisplayRouteInfo is provided by the Rick.Docs.Samples.RouteInfo NuGet package and displays route information.

  • /Products/Details/5模型将的值绑定 id = 5 ,以将 id 参数设置为 5/Products/Details/5 model binds the value of id = 5 to set the id parameter to 5. 有关更多详细信息,请参阅模型绑定See Model Binding for more details.

  • {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 为可选。The ? character in {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.

  • 匹配 URL 路径 /Matches the URL path /.

  • 生成路由值 { controller = Home, action = Index }Produces the route values { controller = Home, action = Index }.

和的值 controlleraction 使用默认值。The values for controller and action make use of the default values. id不会生成值,因为 URL 路径中没有相应的段。id doesn't produce a value since there's no corresponding segment in the URL path. /仅当存在和操作时才匹配 HomeController Index/ only matches if there exists a HomeController and Index action:

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

使用上述控制器定义和路由模板,为 HomeController.Index 以下 URL 路径运行操作:Using the preceding controller definition and route template, the HomeController.Index action is run for the following URL paths:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

URL 路径 / 使用路由模板默认 Home 控制器和 Index 操作。The URL path / uses the route template default Home controllers and Index action. URL 路径 /Home 使用路由模板默认 Index 操作。The URL path /Home uses the route template default Index action.

简便方法 MapDefaultControllerRouteThe convenience method MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

代替Replaces:

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

重要

使用 UseRouting 和中间件配置路由 UseEndpointsRouting is configured using the UseRouting and UseEndpoints middleware. 使用控制器:To use controllers:

传统路由Conventional routing

传统路由用于控制器和视图。Conventional routing is used with controllers and views. default 路由:The default route:

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

是一种传统路由is an example of a conventional routing. 它被称为传统路由,因为它建立了一个 URL 路径约定It's called conventional routing because it establishes a convention for URL paths:

  • 第一个路径段 {controller=Home} 映射到控制器名称。The first path segment, {controller=Home}, maps to the controller name.
  • 第二段 {action=Index} 映射到操作名称。The second segment, {action=Index}, maps to the action name.
  • 第三段 {id?} 用于可选 idThe third segment, {id?} is used for an optional id. ?中的 {id?} 使其成为可选的。The ? in {id?} makes it optional. id用于映射到模型实体。id is used to map to a model entity.

使用此 default 路由,URL 路径:Using this default route, the URL path:

  • /Products/List映射到 ProductsController.List 操作。/Products/List maps to the ProductsController.List action.
  • /Blog/Article/17映射到 BlogController.Article 和通常将参数绑定 id 到17。/Blog/Article/17 maps to BlogController.Article and typically model binds the id parameter to 17.

此映射:This mapping:

  • 基于控制器和操作名称。Is based on the controller and action names only.
  • 不基于命名空间、源文件位置或方法参数。Isn't based on namespaces, source file locations, or method parameters.

通过使用传统路由和默认路由,可以创建应用,而无需为每个操作都提供新的 URL 模式。Using conventional routing with the default route allows creating the app without having to come up with a new URL pattern for each action. 对于具有CRUD样式操作的应用,跨控制器的 url 保持一致:For an app with CRUD style actions, having consistency for the URLs across controllers:

  • 有助于简化代码。Helps simplify the code.
  • 使 UI 更具可预测性。Makes the UI more predictable.

警告

id前面的代码中的将由路由模板定义为可选的。The id in the preceding code is defined as optional by the route template. 无需作为 URL 的一部分提供的可选 ID 即可执行操作。Actions can execute without the optional ID provided as part of the URL. 通常, id 从 URL 中省略时:Generally, whenid is omitted from the URL:

  • id``0由模型绑定设置为。id is set to 0 by model binding.
  • 在数据库匹配中找不到实体 id == 0No entity is found in the database matching id == 0.

特性路由可提供精细的控制,以使某些操作(而不是其他操作)需要 ID。Attribute routing provides fine-grained control to make the ID required for some actions and not for others. 按照约定,文档包含可选参数,如 id 它们可能出现在正确用法中的情况。By convention, the documentation includes optional parameters like id when they're likely to appear in correct usage.

大多数应用应选择基本的描述性路由方案,让 URL 有可读性和意义。Most apps should choose a basic and descriptive routing scheme so that URLs are readable and meaningful. 默认传统路由 {controller=Home}/{action=Index}/{id?}The default conventional route {controller=Home}/{action=Index}/{id?}:

  • 支持基本的描述性路由方案。Supports a basic and descriptive routing scheme.
  • 是基于 UI 的应用的有用起点。Is a useful starting point for UI-based apps.
  • 是许多 web UI 应用程序所需的唯一路由模板。Is the only route template needed for many web UI apps. 对于较大的 web UI 应用,如果经常需要,则使用区域的其他路由。For larger web UI apps, another route using Areas if frequently all that's needed.

MapControllerRouteMapAreaRouteMapControllerRoute and MapAreaRoute :

  • 根据调用的顺序,自动将订单值分配给其终结点。Automatically assign an order value to their endpoints based on the order they are invoked.

ASP.NET Core 3.0 及更高版本中的终结点路由:Endpoint routing in ASP.NET Core 3.0 and later:

  • 没有路由的概念。Doesn't have a concept of routes.
  • 不为执行扩展性提供排序保证,同时处理所有终结点。Doesn't provide ordering guarantees for the execution of extensibility, all endpoints are processed at once.

启用日志记录以查看内置路由实现(如 Route)如何匹配请求。Enable Logging to see how the built-in routing implementations, such as Route, match requests.

属性路由将在本文档的后面部分进行说明。Attribute routing is explained later in this document.

多个传统路由Multiple conventional routes

conventional routes UseEndpoints 通过添加更多对和的调用,可以在内添加多个传统路由 MapControllerRoute MapAreaControllerRouteMultiple conventional routes can be added inside UseEndpoints by adding more calls to MapControllerRoute and MapAreaControllerRoute. 这样做允许定义多个约定,或者添加专用于特定操作的传统路由,例如:Doing so allows defining multiple conventions, or to adding conventional routes that are dedicated to a specific action, such as:

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上述代码中的路由是专用的传统路由The blog route in the preceding code is a dedicated conventional route. 这称为专用的传统路由,因为:It's called a dedicated conventional route because:

因为 controlleraction 不会以参数形式出现在路由模板中 "blog/{*article}"Because controller and action don't appear in the route template "blog/{*article}" as parameters:

  • 它们只能具有默认值 { controller = "Blog", action = "Article" }They can only have the default values { controller = "Blog", action = "Article" }.
  • 此路由始终映射到操作 BlogController.ArticleThis route always maps to the action BlogController.Article.

/Blog/Blog/Article/Blog/{any-string} 是唯一与博客路由匹配的 URL 路径。/Blog, /Blog/Article, and /Blog/{any-string} are the only URL paths that match the blog route.

前面的示例:The preceding example:

  • blog路由具有比路由更高的优先级, default 因为它是首先添加的。blog route has a higher priority for matches than the default route because it is added first.
  • 是一个缩略名称样式路由的示例,在此示例中,通常将项目名称作为 URL 的一部分。Is an example of Slug style routing where it's typical to have an article name as part of the URL.

警告

在 ASP.NET Core 3.0 及更高版本中,路由不会:In ASP.NET Core 3.0 and later, routing doesn't:

  • 定义名为route的概念。Define a concept called a route. UseRouting 向中间件管道添加路由匹配。UseRouting adds route matching to the middleware pipeline. UseRouting中间件会查看应用中定义的终结点集,并根据请求选择最佳的终结点匹配。The UseRouting middleware looks at the set of endpoints defined in the app, and selects the best endpoint match based on the request.
  • 提供可扩展性(如或)的执行 IRouteConstraint 顺序 IActionConstraintProvide guarantees about the execution order of extensibility like IRouteConstraint or IActionConstraint.

有关路由的参考材料,请参阅路由See Routing for reference material on routing.

传统路由顺序Conventional routing order

传统路由仅匹配应用定义的操作和控制器的组合。Conventional routing only matches a combination of action and controller that are defined by the app. 这旨在简化传统路由重叠的情况。This is intended to simplify cases where conventional routes overlap. 使用、和添加路由时, MapControllerRoute MapDefaultControllerRoute 会根据 MapAreaControllerRoute 调用顺序,自动将其分配给终结点。Adding routes using MapControllerRoute, MapDefaultControllerRoute, and MapAreaControllerRoute automatically assign an order value to their endpoints based on the order they are invoked. 与前面显示的路由的匹配具有更高的优先级。Matches from a route that appears earlier have a higher priority. 传统路由依赖于顺序。Conventional routing is order-dependent. 通常情况下,应将具有区域的路由置于更早的位置,因为它们比没有区域的路由更具体。In general, routes with areas should be placed earlier as they're more specific than routes without an area. 使用全部捕获路由参数的专用传统路由 {*article} 可以使路由过于贪婪,这意味着它与你打算与其他路由匹配的 url 相匹配。Dedicated conventional routes with catch-all route parameters like {*article} 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 prevent greedy matches.

警告

由于路由中的 bugcatch-all 参数可能无法正确匹配相应路由。A catch-all parameter may match routes incorrectly due to a bug in routing. 受此 Bug 影响的应用具有以下特征:Apps impacted by this bug have the following characteristics:

  • “全部捕获”路由,例如 {**slug}"A catch-all route, for example, {**slug}"
  • “全部捕获”路由未能匹配应与之匹配的请求。The catch-all route fails to match requests it should match.
  • 删除其他路由可使“全部捕获”路由开始运行。Removing other routes makes catch-all route start working.

请参阅 GitHub bug 1867716579,了解遇到此 bug 的示例。See GitHub bugs 18677 and 16579 for example cases that hit this bug.

.NET Core 3.1.301 SDK 及更高版本中包含此 bug 的修补程序(可选用)。An opt-in fix for this bug is contained in .NET Core 3.1.301 SDK and later. 以下代码设置了一个可修复此 bug 的内部开关:The following code sets an internal switch that fixes this bug:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

解决不明确的操作Resolving ambiguous actions

如果两个终结点通过路由匹配,则路由必须执行下列操作之一:When two endpoints match through routing, routing must do one of the following:

  • 选择最佳的候选项。Choose the best candidate.
  • 引发异常。Throw an exception.

例如:For example:

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

前面的控制器定义了两个匹配的操作:The preceding controller defines two actions that match:

  • URL 路径/Products33/Edit/17The URL path /Products33/Edit/17
  • 路由数据 { controller = Products33, action = Edit, id = 17 }Route data { controller = Products33, action = Edit, id = 17 }.

这是 MVC 控制器的典型模式:This is a typical pattern for MVC controllers:

  • Edit(int)显示用于编辑产品的窗体。Edit(int) displays a form to edit a product.
  • Edit(int, Product)处理已发布的窗体。Edit(int, Product) processes the posted form.

解析正确的路由:To resolve the correct route:

  • Edit(int, Product)当请求为 HTTP 时选择 POSTEdit(int, Product) is selected when the request is an HTTP POST.
  • Edit(int)HTTP 谓词为其他任何内容时,将选择。Edit(int) is selected when the HTTP verb is anything else. Edit(int)通常通过调用 GETEdit(int) is generally called via GET.

HttpPostAttribute提供了 [HttpPost] 用于路由的,以便它可以根据请求的 HTTP 方法进行选择。The HttpPostAttribute, [HttpPost], is provided to routing so that it can choose based on the HTTP method of the request. HttpPostAttribute Edit(int, Product) 比更好匹配 Edit(int)The HttpPostAttribute makes Edit(int, Product) a better match than Edit(int).

了解属性的角色非常重要 HttpPostAttributeIt's important to understand the role of attributes like HttpPostAttribute. 为其他HTTP 谓词定义了类似的属性。Similar attributes are defined for other HTTP verbs. 传统路由中,当操作是显示形式的一部分时,操作通常使用相同的操作名称,即提交窗体工作流。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. 例如,请参阅检查两个编辑操作方法For example, see Examine the two Edit action methods.

如果路由无法选择最佳候选项,则 AmbiguousMatchException 会引发,并列出多个匹配的终结点。If routing can't choose a best candidate, an AmbiguousMatchException is thrown, listing the multiple matched endpoints.

传统路由名称Conventional route names

"blog" "default" 以下示例中的字符串和是传统的路由名称:The strings "blog" and "default" in the following examples are conventional route names:

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

路由名称为路由指定逻辑名称。The route names give the route a logical name. 命名路由可用于生成 URL。The named route can be used for URL generation. 当路由顺序使 URL 生成复杂化时,使用命名路由可简化 URL 创建。Using a named route simplifies URL creation when the ordering of routes could make URL generation complicated. 路由名称必须是唯一的应用程序范围。Route names must be unique application wide.

路由名称:Route names:

  • 不会影响 URL 匹配或处理请求。Have no impact on URL matching or handling of requests.
  • 仅用于生成 URL。Are used only for URL generation.

路由名称概念在路由中表示为IEndpointNameMetadataThe route name concept is represented in routing as IEndpointNameMetadata. 术语路由名称终结点名称The terms route name and endpoint name:

  • 是可互换的。Are interchangeable.
  • 文档和代码中使用哪一个取决于所述的 API。Which one is used in documentation and code depends on the API being described.

REST Api 的属性路由Attribute routing for REST APIs

REST Api 应使用属性路由将应用功能建模为一组资源,其中的操作由HTTP 谓词表示。REST APIs should use attribute routing to model the app's functionality as a set of resources where operations are represented by HTTP verbs.

属性路由使用一组属性将操作直接映射到路由模板。Attribute routing uses a set of attributes to map actions directly to route templates. 下面 StartUp.Configure 是 REST API 的典型代码,并在下一个示例中使用:The following StartUp.Configure code is typical for a REST API and is used in the next sample:

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

在前面的代码中,在 MapControllers 中调用, UseEndpoints 以映射属性路由控制器。In the preceding code, MapControllers is called inside UseEndpoints to map attribute routed controllers.

在以下示例中:In the following example:

  • 使用前面的 Configure 方法。The preceding Configure method is used.
  • HomeController匹配一组与默认传统路由匹配的 Url {controller=Home}/{action=Index}/{id?}HomeController matches a set of URLs similar to what the default conventional route {controller=Home}/{action=Index}/{id?} matches.
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/3The HomeController.Index action is run for any of the URL paths /, /Home, /Home/Index, or /Home/Index/3.

此示例突出显示了属性路由与传统路由之间的主要编程区别。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 and action names play no part in which action is matched, unless token replacement is used. 下面的示例匹配与上一示例相同的 Url:The following example matches the same URLs as the previous example:

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

下面的代码使用和的标记 action 替换 controllerThe following code uses token replacement for action and controller:

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

以下代码适用于 [Route("[controller]/[action]")] 控制器:The following code applies [Route("[controller]/[action]")] to the controller:

[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 方法模板必须在 / 路由模板之前预置或 ~/In the preceding code, the Index method templates must prepend / or ~/ to the route templates. 应用于操作的以 /~/ 开头的路由模板不与应用于控制器的路由模板合并。Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller.

有关路由模板选择的信息,请参阅路由模板优先级See Route template precedence for information on route template selection.

保留的路由名称Reserved routing names

使用控制器或页面时,保留的路由参数名称如下 Razor :The following keywords are reserved route parameter names when using Controllers or Razor Pages:

  • action
  • area
  • controller
  • handler
  • page

使用 page 作为带有属性路由的路由参数是一个常见错误。Using page as a route parameter with attribute routing is a common error. 这样做会导致在 URL 生成时出现不一致的行为。Doing that results in inconsistent and confusing behavior with URL generation.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

URL 生成使用特殊参数名称来确定 URL 生成操作是指引用 Razor 页面还是引用控制器。The special parameter names are used by the URL generation to determine if a URL generation operation refers to a Razor Page or to a Controller.

HTTP 谓词模板HTTP verb templates

ASP.NET Core 具有以下 HTTP 谓词模板:ASP.NET Core has the following HTTP verb templates:

路由模板Route templates

ASP.NET Core 具有以下路由模板:ASP.NET Core has the following route templates:

具有 Http 谓词特性的属性路由Attribute routing with Http verb attributes

请考虑以下控制器:Consider the following controller:

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

在上述代码中:In the preceding code:

  • 每个操作都包含 [HttpGet] 特性,该特性仅将匹配限制为 HTTP GET 请求。Each action contains the [HttpGet] attribute, which constrains matching to HTTP GET requests only.
  • GetProduct操作包括 "{id}" 模板,因此 id 附加到 "api/[controller]" 控制器上的模板。The GetProduct action includes the "{id}" template, therefore id is appended to the "api/[controller]" template on the controller. 方法模板为 "api/[controller]/"{id}""The methods template is "api/[controller]/"{id}"". 因此,此操作仅匹配窗体、、等的 GET 请求 /api/test2/xyz /api/test2/123 /api/test2/{any string}Therefore this action only matches GET requests of for the form /api/test2/xyz,/api/test2/123,/api/test2/{any string}, etc.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • GetIntProduct操作包含 "int/{id:int}") 模板。The GetIntProduct action contains the "int/{id:int}") template. :int模板的部分将 id 路由值限制为可以转换为整数的字符串。The :int portion of the template constrains the id route values to strings that can be converted to an integer. 针对以下内容的 GET 请求 /api/test2/int/abcA GET request to /api/test2/int/abc:
    • 与此操作不匹配。Doesn't match this action.
    • 返回 "找不到 404 " 错误。Returns a 404 Not Found error.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • GetInt2Product操作包含 {id} 在模板中,但不会限制为 id 可转换为整数的值。The GetInt2Product action contains {id} in the template, but doesn't constrain id to values that can be converted to an integer. 针对以下内容的 GET 请求 /api/test2/int2/abcA GET request to /api/test2/int2/abc:
    • 与此路由匹配。Matches this route.
    • 模型绑定无法转换 abc 为整数。Model binding fails to convert abc to an integer. id 方法的参数是整数。The id parameter of the method is integer.
    • 返回400 错误请求,因为模型绑定无法转换 abc 为整数。Returns a 400 Bad Request because model binding failed to convertabc to an integer.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

特性路由可以使用 HttpMethodAttribute HttpPostAttribute 、和等属性 HttpPutAttribute HttpDeleteAttributeAttribute routing can use HttpMethodAttribute attributes such as HttpPostAttribute, HttpPutAttribute, and HttpDeleteAttribute. 所有HTTP 谓词特性都接受路由模板。All of the HTTP verb attributes accept a route template. 下面的示例演示两个匹配同一路由模板的操作:The following example shows two actions that match the same route template:

[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 路径 /products3Using the URL path /products3:

  • MyProductsController.ListProductsHTTP 谓词为时,该操作将运行 GETThe MyProductsController.ListProducts action runs when the HTTP verb is GET.
  • MyProductsController.CreateProductHTTP 谓词为时,该操作将运行 POSTThe MyProductsController.CreateProduct action runs when the HTTP verb is POST.

构建 REST API 时,很少需要 [Route(...)] 在操作方法上使用,因为该操作接受所有 HTTP 方法。When building a REST API, it's rare that you'll need to use [Route(...)] on an action method because the action accepts all HTTP methods. 更好的方法是使用更具体的HTTP 谓词特性来精确了解 API 支持的内容。It's better to use the more specific HTTP verb attribute 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.

REST Api 应使用属性路由将应用功能建模为一组资源,其中的操作由 HTTP 谓词表示。REST APIs should use attribute routing to model the app's functionality as a set of resources where operations are represented by HTTP verbs. 这意味着,多个操作(例如,同一逻辑资源的 GET 和 POST)使用相同的 URL。This means that many operations, for example, GET and POST on the same logical resource use the same URL. 属性路由提供了精心设计 API 的公共终结点布局所需的控制级别。Attribute routing provides a level of control that's needed to carefully design an API's public endpoint layout.

由于属性路由适用于特定操作,因此,使参数变成路由模板定义中的必需参数很简单。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 the following example, id is required as part of the URL path:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Products2ApiController.GetProduct(int)操作:The Products2ApiController.GetProduct(int) action:

  • 是用 URL 路径(如)运行的/products2/3Is run with URL path like /products2/3
  • 不以 URL 路径运行 /products2Isn't run with the URL path /products2.

使用 [Consumes] 属性,操作可以限制支持的请求内容类型。The [Consumes] attribute allows an action to limit the supported request content types. 有关详细信息,请参阅使用 "使用" 属性定义支持的请求内容类型For more information, see Define supported request content types with the Consumes attribute.

请参阅路由了解路由模板和相关选项的完整说明。See Routing for a full description of route templates and related options.

有关的详细信息 [ApiController] ,请参阅ApiController 属性For more information on [ApiController], see ApiController attribute.

路由名称Route name

以下代码定义 的Products_List路由名称:The following code defines a route name of Products_List:

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

可以使用路由名称基于特定路由生成 URL。Route names can be used to generate a URL based on a specific route. 路由名称:Route names:

  • 不会影响路由的 URL 匹配行为。Have no impact on the URL matching behavior of routing.
  • 仅用于生成 URL。Are only used for URL generation.

路由名称必须在应用程序范围内唯一。Route names must be unique application-wide.

对比前面的代码和传统的默认路由,后者将 id 参数定义为可选( {id?} )。Contrast the preceding code with the conventional default route, which defines the id parameter as optional ({id?}). 精确指定 Api 的功能具有多种优点,例如允许 /products/products/5 调度到不同的操作。The ability to precisely specify APIs has advantages, such as allowing /products and /products/5 to be dispatched to different actions.

组合特性路由Combining attribute 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.

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

在上面的示例中:In the preceding example:

  • URL 路径 /products 可以匹配ProductsApi.ListProductsThe URL path /products can match ProductsApi.ListProducts
  • URL 路径 /products/5 可以匹配 ProductsApi.GetProduct(int)The URL path /products/5 can match ProductsApi.GetProduct(int).

这两个操作仅匹配 HTTP, GET 因为它们用特性标记 [HttpGet]Both of these actions only match HTTP GET because they're marked with the [HttpGet] attribute.

应用于操作的以 /~/ 开头的路由模板不与应用于控制器的路由模板合并。Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller. 下面的示例匹配一组类似于默认路由的 URL 路径。The following example matches a set of URL paths similar to the default route.

[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] 上述代码中的属性:The following table explains the [Route] attributes in the preceding code:

属性Attribute 结合[Route("Home")]Combines with [Route("Home")] 定义路由模板Defines route template
[Route("")] “是”Yes "Home"
[Route("Index")] “是”Yes "Home/Index"
[Route("/")] No ""
[Route("About")] “是”Yes "Home/About"

属性路由顺序Attribute route order

路由构建树并同时匹配所有终结点:Routing builds a tree and matches all endpoints simultaneously:

  • 路由条目的行为方式与置于理想排序中的行为相同。The route entries behave as if 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, an attribute route like blog/search/{topic} is more specific than an attribute route like blog/{*article}. blog/search/{topic}默认情况下,路由具有更高的优先级,因为它更为具体。The blog/search/{topic} route has higher priority, by default, because it's more specific. 使用传统路由,开发人员负责按所需的顺序放置路由。Using conventional routing, the developer is responsible for placing routes in the desired order.

属性路由可以使用属性配置订单 OrderAttribute routes can configure an order using the Order property. 提供的所有路由属性都包括 OrderAll of the framework provided route attributes include Order . 路由按 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 runs before routes that don't set an order. 使用 Order = 1 默认路由排序后的运行设置路由。Setting a route using Order = 1 runs after default route ordering.

避免依赖于 OrderAvoid depending on Order. 如果应用的 URL 空间需要显式顺序值才能正确路由,则很可能会使客户端混淆。If an app's URL-space requires explicit order values to route correctly, then it's likely confusing to clients as well. 通常,属性路由选择 URL 匹配的正确路由。In general, attribute routing selects the correct route with URL matching. 如果用于 URL 生成的默认顺序不起作用,则使用路由名称作为替代通常比应用属性更简单 OrderIf the default order used for URL generation isn't working, using a route name as an override is usually simpler than applying the Order property.

请考虑以下两个控制器,它们都定义了路由匹配 /homeConsider the following two controllers which both define the route matching /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使用前面的代码请求将引发异常,如下所示:Requesting /home with the preceding code throws an exception similar to the following:

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

添加 Order 到其中一个路由属性可解决歧义:Adding Order to one of the route attributes resolves the ambiguity:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

在前面的代码中, /home 运行 HomeController.Index 终结点。With the preceding code, /home runs the HomeController.Index endpoint. 若要转到 MyDemoController.MyIndex ,请请求 /home/MyIndexTo get to the MyDemoController.MyIndex, request /home/MyIndex. 注意Note:

  • 上面的代码是一个示例或不良路由设计。The preceding code is an example or poor routing design. 它用于说明 Order 属性。It was used to illustrate the Order property.
  • Order属性只解析多义性,该模板无法匹配。The Order property only resolves the ambiguity, that template cannot be matched. 最好删除该 [Route("Home")] 模板。It would be better to remove the [Route("Home")] template.

请参阅 Razor 页面路由和应用约定:路由顺序获取有关页面的路由顺序的信息 Razor 。See Razor Pages route and app conventions: Route order for information on route order with Razor Pages.

在某些情况下,将返回具有不明确路由的 HTTP 500 错误。In some cases, an HTTP 500 error is returned with ambiguous routes. 使用日志记录查看导致的终结点 AmbiguousMatchExceptionUse logging to see which endpoints caused the AmbiguousMatchException.

路由模板中的标记替换 [控制器],[操作],[区域]Token replacement in route templates [controller], [action], [area]

为方便起见,特性路由支持为保留路由参数替换标记,方法是将令牌括在以下其中一项:For convenience, attribute routes support token replacement for reserved route parameters by enclosing a token in one of the following:

  • 方括号:[]Square brackets: []
  • 大括号:{}Curly 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:

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

在上述代码中:In the preceding code:

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • /Products0/ListMatches /Products0/List
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • /Products0/Edit/{id}Matches /Products0/Edit/{id}

标记替换发生在属性路由生成的最后一步。Token replacement occurs as the last step of building the attribute routes. 前面的示例与下面的代码具有相同的行为:The preceding example behaves the same as the following code:

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 讨论问题中告诉我们,如果你想要在本地语言中查看代码注释。If you are reading this in a language other than English, let us know in this GitHub discussion issue if you'd like to see the code comments in your native language.

属性路由还可以与继承结合使用。Attribute routes can also be combined with inheritance. 这与标记替换功能功能强大。This is powerful combined with token replacement. 标记替换也适用于属性路由定义的路由名称。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:

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

标记替换也适用于属性路由定义的路由名称。Token replacement also applies to route names defined by attribute routes. [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:

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 是应用程序模型约定,可以: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]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

前面的 ListAll 方法匹配 /subscription-management/list-allThe preceding ListAll method matches /subscription-management/list-all.

RouteTokenTransformerConventionConfigureServices 中注册为选项。The RouteTokenTransformerConvention is registered as an option in ConfigureServices.

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

有关信息区的定义,请参阅网站上的 MDN web 文档See MDN web docs on Slug for the definition of Slug.

警告

如果使用 System.Text.RegularExpressions 处理不受信任的输入,则传递一个超时。When using System.Text.RegularExpressions to process untrusted input, pass a timeout. 恶意用户可能会向 RegularExpressions 提供输入,从而导致拒绝服务攻击A malicious user can provide input to RegularExpressions causing a Denial-of-Service attack. 使用 RegularExpressions 的 ASP.NET Core 框架 API 会传递一个超时。ASP.NET Core framework APIs that use RegularExpressions pass a timeout.

多个属性路由Multiple attribute 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 Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

在控制器上放置多个路由属性意味着每个属性都与操作方法上的每个路由属性结合:Putting multiple route attributes on the controller means that each one combines with each of the route attributes on the action methods:

[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 谓词路由约束都实现 IActionConstraintAll the HTTP verb route constraints implement IActionConstraint.

当实现的多个路由属性 IActionConstraint 放置在一个操作上时:When multiple route attributes that implement IActionConstraint are placed on an action:

  • 每个操作约束都与应用于控制器的路由模板结合。Each action constraint combines with the route template applied to the controller.
[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 空间基本且定义完善。Using multiple routes on actions might seem useful and powerful, it's better to keep your app's URL space basic 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.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

在上面的代码中, [HttpPost("product/{id:int}")] 应用路由约束。In the preceding code, [HttpPost("product/{id:int}")] applies a route constraint. ProductsController.ShowProduct 操作仅由类似的 URL 路径进行匹配 /product/3The ProductsController.ShowProduct action is matched only by URL paths like /product/3. 路由模板部分 {id:int} 仅限制整数。The route template portion {id:int} constrains that segment to only integers.

有关路由模板语法的详细说明,请参阅路由模板参考See Route Template Reference for a detailed description of route template syntax.

使用 IRouteTemplateProvider 的自定义路由属性Custom route attributes using IRouteTemplateProvider

所有路由特性都实现 IRouteTemplateProviderAll of the route attributes implement IRouteTemplateProvider. ASP.NET Core 运行时:The ASP.NET Core runtime:

  • 应用启动时,在控制器类和操作方法上查找属性。Looks for attributes on controller classes and action methods when the app starts.
  • 使用实现 IRouteTemplateProvider 以生成初始路由集的特性。Uses the attributes that implement IRouteTemplateProvider to build the initial set of routes.

实现 IRouteTemplateProvider 以定义自定义路由属性。Implement IRouteTemplateProvider to define custom 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 => 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/MyTestApiThe preceding Get method returns Order = 2, Template = api/MyTestApi.

使用应用程序模型自定义属性路由Use application model to customize attribute routes

应用程序模型:The application model:

  • 是在启动时创建的对象模型。Is an object model created at startup.
  • 包含 ASP.NET Core 用来在应用程序中路由和执行操作的所有元数据。Contains all of the metadata used by ASP.NET Core to route and execute the actions in an app.

应用程序模型包括从路由属性收集的所有数据。The application model includes all of the data gathered from route attributes. 路由属性中的数据由 IRouteTemplateProvider 实现提供。The data from route attributes is provided by the IRouteTemplateProvider implementation. 规范Conventions:

  • 可以编写来修改应用程序模型,以自定义路由的行为方式。Can be written to modify the application model to customize how routing behaves.
  • 在应用程序启动时读取。Are read at app startup.

本部分显示了使用应用程序模型自定义路由的基本示例。This section shows a basic example of customizing routing using application model. 下面的代码使路由大致与项目的文件夹结构对齐。The following code makes routes roughly line up with the folder structure of the project.

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 约定应用到已路由属性的控制器:The following code prevents the namespace convention from being applied to controllers that are attribute routed:

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

例如,以下控制器不使用 NamespaceRoutingConventionFor example, the following controller doesn't use 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 方法:The NamespaceRoutingConvention.Apply method:

  • 如果控制器为属性路由,则不执行任何操作。Does nothing if the controller is attribute routed.
  • 基于删除基的控制器模板 namespace namespaceSets the controllers template based on the namespace, with the base namespace removed.

NamespaceRoutingConvention可应用于 Startup.ConfigureServicesThe NamespaceRoutingConvention can be applied in Startup.ConfigureServices:

namespace My.Application
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Conventions.Add(
                    new NamespaceRoutingConvention(typeof(Startup).Namespace));
            });
        }
        // Remaining code ommitted for brevity.

例如,请考虑以下控制器:For example, consider the following controller:

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

在上述代码中:In the preceding code:

  • 基数 namespaceMy.ApplicationThe base namespace is My.Application.
  • 前面的控制器的全名为 My.Application.Admin.Controllers.UsersControllerThe full name of the preceding controller is My.Application.Admin.Controllers.UsersController.
  • NamespaceRoutingConvention 控制器模板设置为 Admin/Controllers/Users/[action]/{id?The NamespaceRoutingConvention sets the controllers template to Admin/Controllers/Users/[action]/{id?.

NamespaceRoutingConvention还可以应用为控制器上的属性:The NamespaceRoutingConvention can also be applied as an attribute on a controller:

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

混合路由:属性路由与传统路由Mixed routing: Attribute routing vs conventional routing

ASP.NET Core 应用可以混合使用传统路由和属性路由。ASP.NET Core apps 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.

属性路由和传统路由使用相同的路由引擎。Attribute routing and conventional routing use the same routing engine.

URL 生成和环境值URL Generation and ambient values

应用可以使用路由 URL 生成功能来生成指向操作的 URL 链接。Apps can use routing URL generation features to generate URL links to actions. 生成 Url 可消除硬编码 Url,使代码更可靠和更易于维护。Generating URLs eliminates hardcoding URLs, making code more robust and maintainable. 本部分重点介绍 MVC 提供的 URL 生成功能,仅介绍 URL 生成的工作原理的基础知识。This section focuses on the URL generation features provided by MVC and 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 element of infrastructure between MVC and routing for URL generation. 的实例 IUrlHelper 可通过 " Url 控制器"、"视图" 和 "视图" 组件中的属性使用。An instance of IUrlHelper is available through the Url property in controllers, views, and view components.

在下面的示例中, IUrlHelper 通过属性使用接口 Controller.Url 生成其他操作的 URL。In the following example, the IUrlHelper interface is used through the Controller.Url property to generate a URL to another action.

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/DestinationIf the app is using the default conventional route, the value of the url variable is the URL path string /UrlGeneration/Destination. 此 URL 路径由路由通过组合创建:This URL path is created by routing by combining:

  • 当前请求中的路由值,称为 "环境值"。The route values from the current request, which are called ambient values.
  • 传递到的值 Url.Action ,并将这些值替换为路由模板: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. 不具有值的路由参数可以:A route parameter that doesn't have a value can:

  • 如果有一个默认值,则使用默认值。Use a default value if it has one.
  • 如果是可选的,则跳过它。Be skipped if it's optional. 例如, id 路由模板中的 {controller}/{action}/{id?}For example, the id from the route template {controller}/{action}/{id?}.

如果任何所需的路由参数没有对应的值,URL 生成将失败。URL generation fails 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 假定传统路由The preceding example of Url.Action assumes conventional routing. URL 生成的工作方式类似于属性路由,但概念不同。URL generation works similarly with attribute routing, though the concepts are different. 对于传统路由:With conventional routing:

  • 路由值用于扩展模板。The route values are used to expand a template.
  • 和的路由值 controller action 通常出现在该模板中。The route values for controller and action usually appear in that template. 这是因为由路由匹配的 Url 遵循约定。This works because the URLs matched by routing adhere to a convention.

下面的示例使用属性路由:The following example uses attribute routing:

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/destinationThe Source action in the preceding code generates custom/url/to/destination.

LinkGenerator已添加到 ASP.NET Core 3.0 中作为的替代项 IUrlHelperLinkGenerator was added in ASP.NET Core 3.0 as an alternative to IUrlHelper. LinkGenerator提供类似但更灵活的功能。LinkGenerator offers similar but more flexible functionality. 上的每个方法 IUrlHelper 也具有相应的一系列方法 LinkGeneratorEach method on IUrlHelper has a corresponding family of methods on LinkGenerator as well.

根据操作名称生成 URLGenerating URLs by action name

LinkGenerator、GetPathByAction和所有相关重载都旨在通过指定控制器名称和操作名称来生成目标终结点。Url.Action, LinkGenerator.GetPathByAction, and all related overloads all are designed to generate the target endpoint by specifying a controller name and action name.

使用时 Url.Action ,和的当前路由值 controller action 由运行时提供:When using Url.Action, the current route values for controller and action are provided by the runtime:

  • 和的值 controller action 都属于环境值和值。The value of controller and action are part of both ambient values and values. 方法 Url.Action 始终使用和的当前值, action controller 并生成路由到当前操作的 URL 路径。The method Url.Action always uses the current values of action and controller and generates a URL path that routes to the current action.

路由尝试使用环境值中的值来填充生成 URL 时未提供的信息。Routing attempts to use the values in ambient values to fill in information that wasn't provided when generating a URL. 请考虑类似于 {a}/{b}/{c}/{d} 环境值的路由 { a = Alice, b = Bob, c = Carol, d = David }Consider a route like {a}/{b}/{c}/{d} with ambient values { a = Alice, b = Bob, c = Carol, d = David }:

  • 路由具有足够的信息来生成 URL,无需任何其他值。Routing has enough information to generate a URL without any additional values.
  • 路由具有足够的信息,因为所有路由参数都具有值。Routing has enough information because all route parameters have a value.

如果添加了值 { d = Donovan }If the value { d = Donovan } is added:

  • { d = David } 被忽略。The value { d = David } is ignored.
  • 生成的 URL 路径为 Alice/Bob/Carol/DonovanThe generated URL path is Alice/Bob/Carol/Donovan.

警告: URL 路径是分层的。Warning: URL paths are hierarchical. 在前面的示例中,如果添加了值 { c = Cheryl }In the preceding example, if the value { c = Cheryl } is added:

  • 这两个值 { c = Carol, d = David } 都将被忽略。Both of the values { c = Carol, d = David } are ignored.
  • 不再存在的值 d ,URL 生成将失败。There is no longer a value for d and URL generation fails.
  • c d 若要生成 URL,必须指定和的所需值。The desired values of c and d must be specified to generate a URL.

你可能希望在默认路由中遇到此问题 {controller}/{action}/{id?}You might expect to hit this problem with the default route {controller}/{action}/{id?}. 此问题在实践中很罕见,因为 Url.Action 始终显式指定 controlleraction 值。This problem is rare in practice because Url.Action always explicitly specifies a controller and action value.

多个Url 重载。操作采用路由值对象为除和以外的路由参数提供值 controller actionSeveral overloads of Url.Action take a route values object to provide values for route parameters other than controller and action. 路由值对象经常与一起使用 idThe route values object is frequently used with id. 例如 Url.Action("Buy", "Products", new { id = 17 })For example, Url.Action("Buy", "Products", new { id = 17 }). 路由值对象:The route values object:

  • 按约定通常是匿名类型的对象。By convention is usually an object of anonymous type.
  • 可以是, IDictionary<> 也可以是POCOCan be an IDictionary<> or a POCO).

任何与路由参数不匹配的附加路由值都放在查询字符串中。Any additional route values that don't match route parameters are put in the query string.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url);
}

前面的代码生成 /Products/Buy/17?color=redThe preceding code generates /Products/Buy/17?color=red.

下面的代码生成一个绝对 URL:The following code generates an absolute 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,请使用以下项之一:To create an absolute URL, use one of the following:

  • 接受的重载 protocolAn overload that accepts a protocol. 例如,前面的代码。For example, the preceding code.
  • 默认情况下, LinkGenerator生成绝对 uri。LinkGenerator.GetUriByAction, which generates absolute URIs by default.

按路由生成 UrlGenerate URLs by route

前面的代码演示了如何通过传入控制器和操作名称来生成 URL。The preceding code demonstrated generating a URL by passing in the controller and action name. IUrlHelper还提供了RouteUrl系列方法。IUrlHelper also provides the Url.RouteUrl family of methods. 这些方法类似于Url. 操作,但它们不会将和的当前值 action 复制 controller 到路由值。These methods are similar to Url.Action, but they don't copy the current values of action and controller to the route values. 最常见的用法是 Url.RouteUrlThe most common usage of Url.RouteUrl:

  • 指定用于生成 URL 的路由名称。Specifies a route name to generate the URL.
  • 通常不指定控制器或操作名称。Generally doesn't specify a controller or action name.
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 文件生成一个到的 HTML 链接 Destination_RouteThe following Razor file generates an HTML link to the Destination_Route:

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

在 HTML 和中生成 UrlRazorGenerate URLs in HTML and Razor

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 操作方法来生成 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 Tag Helpers in 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.

操作结果中的 URL 生成URL generation in Action Results

前面的示例演示了如何 IUrlHelper 在控制器中使用。The preceding examples showed using IUrlHelper in a controller. 控制器中最常见的用法是将 URL 生成为操作结果的一部分。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:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

操作将生成工厂方法(如 RedirectToAction 和),并 CreatedAtAction 遵循中的方法 IUrlHelperThe action results factory methods such as RedirectToAction and CreatedAtAction 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 following example, the route named blog is a dedicated conventional route:

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") 使用路由生成 URL 路径 / default ,但为什么要这样做呢?Using the preceding route definitions, Url.Action("Index", "Home") generates the URL path / using the default route, but why? 用户可能认为使用 blog,路由值 { controller = Home, action = Index } 就足以生成 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. 使用的 URL 生成 blog 失败,因为这些值 { controller = Home, action = Index } 不匹配 { controller = Blog, action = Article }URL generation using blog fails because the values { controller = Home, action = Index } don't match { controller = Blog, action = Article }. 然后,路由回退,尝试使用 default,并最终成功。Routing then falls back to try default, which succeeds.

AreasAreas

区域是一项 MVC 功能,用于将相关功能作为一个单独的组组织到一个组中:Areas are an MVC feature used to organize related functionality into a group as a separate:

  • 控制器操作的路由命名空间。Routing namespace for controller actions.
  • 视图的文件夹结构。Folder structure for views.

通过使用区域,应用可以有多个具有相同名称的控制器,只要它们具有不同的区域即可。Using areas allows an app to have multiple controllers with the same name, as long as they have different areas. 通过向 controlleraction 添加另一个路由参数 area,可使用区域为路由创建层次结构。Using areas creates a hierarchy for the purpose of routing by adding another route parameter, area to controller and action. 本部分讨论路由如何与区域交互。This section discusses how routing interacts with areas. 有关如何将区域与视图结合使用的详细信息,请参阅区域See Areas for details about how areas are used with views.

下面的示例将 MVC 配置为使用默认传统路由,并为 area 命名的 area 指定路由 BlogThe following example configures MVC to use the default conventional route and an area route for an area named Blog:

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

在前面的代码中, MapAreaControllerRoute 将调用来创建 "blog_route"In the preceding code, MapAreaControllerRoute is called to create the "blog_route". 第二个参数 "Blog" 为区域名称。The second parameter, "Blog", is the area name.

当匹配 URL 路径(如 /Manage/Users/AddUser )时, "blog_route" 路由将生成路由值 { area = Blog, controller = Users, action = AddUser }When matching a URL path like /Manage/Users/AddUser, the "blog_route" route generates the route values { area = Blog, controller = Users, action = AddUser }. area路由值由的默认值生成 areaThe area route value is produced by a default value for area. 创建的路由 MapAreaControllerRoute 等效于以下内容:The route created by MapAreaControllerRoute is equivalent to the following:

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 提供默认值和约束,来创建路由。MapAreaControllerRoute creates a route using both a default value and constraint for area using the provided area name, in this case Blog. 默认值确保路由始终生成 { area = Blog, ... },约束要求在生成 URL 时使用值 { area = Blog, ... }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 as they're more specific than routes without an area.

使用前面的示例,路由值 { area = Blog, controller = Users, action = AddUser } 与以下操作匹配:Using the preceding example, the route values { area = Blog, controller = Users, action = AddUser } match the following action:

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]特性用于将控制器表示为区域的一部分。The [Area] attribute is what denotes a controller as part of an area. 此控制器在区域中 BlogThis controller is in the Blog area. 没有属性的控制器 [Area] 不是任何区域的成员,并且在not area 路由值由路由提供时不匹配。Controllers without an [Area] attribute are not members of any area, and do 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
    {
        // 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}");
        }
    }
}

此处显示每个控制器的命名空间,以获取完整性。The namespace of each controller is shown here for completeness. 如果前面的控制器使用相同的命名空间,则会生成编译器错误。If the preceding controllers uses the same namespace, a compiler error would be generated. 类命名空间对 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 is 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.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);
        }
    }
}

下面的代码生成一个 URL,用于 /Zebra/Users/AddUserThe following code generates a URL to /Zebra/Users/AddUser:

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

操作定义Action definition

控制器上的公共方法(具有NonAction特性的方法除外)是操作。Public methods on a controller, except those with the NonAction attribute, are actions.

示例代码Sample code

调试诊断Debug diagnostics

要获取详细的路由诊断输出,请将 Logging:LogLevel:Microsoft 设置为 DebugFor detailed routing diagnostic output, set Logging:LogLevel:Microsoft to Debug. 在开发环境中,将appsettings.Development.js上的日志级别设置为:In the development environment, set the log level in appsettings.Development.json:

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

ASP.NET Core MVC 使用路由中间件来匹配传入请求的 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?}" 可以匹配诸如 /Products/Details/5 之类的 URL 路径,并通过对路径进行标记来提取路由值 { 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 的控制器并运行 Details 操作:MVC will attempt to locate a controller named ProductsController and run the action Details:

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

请注意,在此示例中,当调用此操作时,模型绑定会使用值 id = 5id 参数设置为 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

  • /

简便方法 UseMvcWithDefaultRouteThe 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 add 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?}");

前面的代码是传统路由的示例。The preceding code is an example of a conventional routing. 此样式称为传统路由,因为它建立了一个 URL 路径约定This style is called 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. id映射到模型实体。id maps 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 中提供 ID 也可执行操作。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. 属性路由可以提供细化控制,使某些操作需要 ID,某些操作不需要 ID。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.Article 操作。Since 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} 来捕获 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 将匹配一组 URL,这组 URL 与默认路由 {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();
   }
}

将针对任意 URL 路径 //Home/Home/Index 执行 HomeController.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(...)]这是因为该操作将接受所有 HTTP 方法。When building a REST API, it's rare that you will want to use [Route(...)] on an action method as the action will accept all HTTP methods. 建议使用更特定的 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) { ... }
}

将针对诸如 /products/3(而非 /products)之类的 URL 路径执行 ProductsApi.GetProduct(int) 操作。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). 这两项操作仅匹配 HTTP GET,因为它们用 HttpGetAttribute 标记。Both of these actions only match HTTP GET because they're marked 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 主题中提供了 Razor 页面路由和应用约定:路由顺序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-brackets ([, ]). 标记 [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() { ... }
}

RouteTokenTransformerConventionConfigureServices 中注册为选项。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. 当应用启动时,MVC 会查找控制器类和操作方法上的属性,并使用可实现 IRouteTemplateProvider 的属性生成一组初始路由。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 接口用于生成 URL,是 MVC 与路由之间的基础结构的基础部分。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);
}

操作结果工厂方法遵循与 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? 用户可能认为使用 blog,路由值 { controller = Home, action = Index } 就足以生成 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.

AreasAreas

区域是一种 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. 通过向 controlleraction 添加另一个路由参数 area,可使用区域为路由创建层次结构。Using 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.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

与 URL 路径(比如 /Manage/Users/AddUser)匹配时,第一个路由将生成路由值 { 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:

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, ... },约束要求在生成 URL 时使用值 { area = Blog, ... }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
    {
        // 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}");
        }        
    }
}

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

备注

出于完整性考虑,此处显示了每个控制器的命名空间,否则,控制器会发生命名冲突并生成编译器错误。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.

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

了解 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. 因此,假定 Http 谓词为 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 方法,并为要执行的约束选择“顺序”。You are responsible for implementing the Accept method and choosing an 'Order' for the constraint to execute. 在此例中,当 country 路由值匹配时,Accept 方法返回 true 以表示该操作是匹配项。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.