概述

URL 路由(Routing)决定了 ASP.NET Core Web 程序的映射。因此,一套精心设计的 URL 路由系统对我们的程序非常重要。

我们将在本节学习 URL 路由的概念以及如何在 Web 程序中使用 URL 路由。

什么是 URL 路由

URL 路由(或路由)负责将传入请求映射到路由处理程序。通常,路由处理程序都是一个 Action。

路由描述了 URL 应该如何映射到 Action,并且它不区分大小写。ASP.NET Core 使用 路由中间件 将传入请求的 URL 与预定义的路由进行匹配;然后,有效的 URL 将被映射到目标 Action。最终触发目标 Action。

注:在本课中讨论 URL 时,我们通常会忽略完整 URL 的协议和主机名部分。即对于完整的 URL http://localhost:5000/product/update/101,我们通常只关注 product/update/101 这一部分。

ASP.NET Core 支持两种类型的路由:

  • 约定路由(Conventional routing):约定路由使用约定来定义路由。在日常交流中,约定、URL 模板、路由模板都指约定路由。最典型的约定就是 {controller=Home}/{action=Index}/{id?}

  • 特性路由(Attribute routing):特性路由通过标注特性将 Action 直接映射到路由模板。例如,如果一个控制器类被 [Route("Product")] 标注了,则所有以 Product 开头的 URL 都将导航至该控制器。如果再用 [Route("Create")] 标注此控制器中的 Action,则 product/create 将导航至该 Action。

使用 ASP.NET Core 框架,我们即可以开发 Web 程序也可以开发 Web API 程序。开发 Web API 程序时,只能使用特性路由。开发 Web 程序时,约定路由和特性路由都能用,但实际项目中,我们从不将这两种路由一起使用 。

本课程的重点是约定路由,关于特性路由的知识请参考 Build Web APIs using ASP.NET

约定路由

创建 ASP.NET Core 项目后,你可以在根目录下找到 Startup.cs。该文件内是名为 Startup 的类。当 Web 程序启动时,将调用此类的方法,并在此处配置许多关键选项,包括配置约定路由。

Startup 类的 Configure 方法中包含以下代码:

  1. app.UseMvc(routes =>
  2. {
  3. routes.MapRoute(
  4. name: "default",
  5. template: "{controller=Home}/{action=Index}/{id?}");
  6. });

这段代码做了两件事:

  1. 通知 Web 程序加载 MVC 中间件。如果没有段代码,程序都不算是 MVC 程序

  2. 将名为 default 的路由添加到其约定路由系统

路由模板

字符串 "{controller=Home}/{action=Index}/{id?}" 就是 default 路由的路由模板。此模板指导路由系统如何将传入的 HTTP 请求映射到目标控制器和 Action。

路由模板的语法非常简单,学会以下几点便可处理可能遇到的大多数情况:

  1. 路由模板中只有两种内容 —— 路由参数和文本字符串

    • 路由参数是花括号项,例如 {controller}、{action} 和 {id}

    • 文本字符串是除路由参数之外的所有内容。例如,在模板 search/{year}/{file}.{ext} 中,红色的部分都是文本字符串

    • 不同路由参数间必须用文本字符串分隔,否则在程序启动时就会抛出异常

  2. 路由参数有一些约束和装饰器:

    • {controller} 和 {action} 是两个特殊参数,它们的值将分别映射到目标控制器名和目标 Action 名

    • 将 {controller} 参数映射到目标控制器类时,将忽略类名称的 Controller 后缀(如果有)。例如,如果 {controller} 参数设为 Product,则它将匹配 ProductController 类或 [Controller] 标注的 Product 类或继承自 Microsoft.AspNetCore.Mvc.Controller 的 Product 类

    • 除 {controller} 和 {action} 外,默认情况下,其他参数将映射到目标 Action 的参数

    • ? 后缀表示参数可省略,例如 {id?}

    • 路由参数可以设置默认值,例如 {controller = Home} 中的 Home 和 {action = Index} 中的 Index

    • 您可以通过添加约束来限制路由参数。例如 {id:int} 将参数限制为仅接受整数值,{ssn:regex(^\d{{3}}-\d{{2}}-\d{{4}}$)} 要求参数 {ssn} 的值满足正则表达式。你还可以使用多个约束来限制参数,例如 {password:minlength(6):maxlength(20)}

了解上面的知识点后,让我们再深入研究一下 {controller=Home}/{action=Index}/{id?} 模板:

URL 意图 是否有效 注解 映射至
http://localhost:5000 访问网站主页 Yes 省略了控制器名、Action 名和 ID 参数 HomeController‘s Index() 方法
http://localhost:5000/ 访问网站根目录 Yes 省略了控制器名、Action 名和 ID 参数 HomeController‘s Index() 方法
http://localhost:5000/home 访问网站主页 Yes 省略了 Action 名和 ID 参数 HomeController‘s Index() 方法
http://localhost:5000/home/index 访问 home/index Yes 只省略了 ID 参数 HomeController‘s Index() 方法
http://localhost:5000/home/index/101 访问 home/index 附带 ID 参数 Yes 所有的参数都设置了 HomeController‘s Index(int id) 方法
http://localhost:5000/home/index?id=101 访问 home/index 附带 ID 参数 Yes ID 参数通过 query string 传递 HomeController‘s Index(int id)方法
http://localhost:5000/product 展示一些产品信息 Yes 省略了 Action 名和 ID 参数 ProductController‘s Index 方法
http://localhost:5000/product/showavailable 展示所有有效的产品信息 Yes 因为不需要 ID 参数,所以省略了 ProductController‘s ShowAvailable 方法
http://localhost:5000/product/showdetail/101 展示 ID 为 101 的产品的详细信息 Yes 所有参数都设置了 ProductController‘s ShowDetail(int id) 方法
http://localhost:5000/product/search?kw=bike 以 bike 为关键字进行搜索 Yes 此处不许要 ID 参数,但目标方法需要一个名为 kw 的参数 ProductController‘s Search(string kw) 方法
http://localhost:5000/101 省略控制器名和 Action 名,访问 HomeControllerIndex(int id) 方法,ID 参数为 101 Yes 404 Not Found 错误 没有位于 101Controller 控制器名为 Index 的 Action
http://localhost:5000/Index/101 省略控制器名,访问 HomeControllerIndex(int id) 方法,ID 参数为 101 Yes 404 Not Found 错误 没有位于 IndexController 控制器名为 101 的 Action
http://localhost:5000///101 省略控制器名和 Action 名,访问 HomeControllerIndex(int id)方法,ID 参数为 101 No 无效的 HTTP URL
http://localhost:5000//Index/101 省略控制器名,访问 HomeControllerIndex(int id) 方法,ID 参数为 101 No 无效的 HTTP URL

定义多路由

通常,默认路由足以应对大多数情况,但有时我们仍需要定义其他路由来处理特殊情况。

定义两个自定义路由:

  1. app.UseMvc(routes =>
  2. {
  3. routes.MapRoute(
  4. name: "default",
  5. template: "{controller=Home}/{action=Index}/{id?}");
  6. routes.MapRoute(
  7. name: "productSearch",
  8. template: "search/{kw?}",
  9. defaults: new { controller = "Product", action = "Search" });
  10. routes.MapRoute(
  11. name: "docSearch",
  12. template: "help/{kw?}",
  13. defaults: new { controller = "Help", action = "FullTextSearch" });
  14. });

productSearch 路由将 search/{kw?} URL 导航至 Product 控制器的 Search 方法,docSearch 同理。

指定 Action 的 HTTP 方法

通常,对于 Create、Update 和 Delete 这类操作,我们都会创建操作对。操作对就是两个重载的 Action,一个负责将用户导航至视图,另一个负责执行实际操作。例如:

  1. public class ProductController : Controller
  2. {
  3. public IActionResult Create()
  4. {
  5. return View();
  6. }
  7. public IActionResult Create(Product p)
  8. {
  9. // business logic ...
  10. return View(p);
  11. }
  12. public IActionResult Update()
  13. {
  14. return View();
  15. }
  16. public IActionResult Update(Product p)
  17. {
  18. // business logic ...
  19. return View(p);
  20. }
  21. ...
  22. }

Create() 将用户导航至创建产品的页面。Create(Product p) 接收用户填写的产品信息,创建产品。Update 的机制与 Create 相同。

在大多数情况下,路由系统可以通过检查参数列表找到目标 Action,但有时参数列表不足以让路由系统明确目标 Action。因为,我们须要使用 HTTP 方法特性标注 Action,例如 [HttpGet] 和 [HttpPost]。

注:当用户使用浏览器与 Web 程序(运行在服务器上)进行交互时,他们的大多数操作都是单击超链接,提交表单或使用地址栏进行导航。地址栏和超链接只能发送 HTTP GET 请求,HTML 表单可以发送 HTTP POST 或 HTTP GET 请求。这意味着,在 Web 程序中,我们仅需使用 [HttpGet] 和 [HttpPost] 特性标注 Action。如果你在 Web 程序中使用 AJAX,请始终将服务器端逻辑与 Web API 程序分开。

特性 功能
[HttpGet]
- 展示数据,例如显式创建、更新、删除等页面


- 搜索页面
| | [HttpPost] |
- 实际执行增删查改
- 执行搜索操作
|

添加了 HTTP 方法特性标注的 Action:

  1. public class ProductController : Controller
  2. {
  3. [HttpGet]
  4. public IActionResult Create()
  5. {
  6. return View();
  7. }
  8. [HttpPost]
  9. public IActionResult Create(Product p)
  10. {
  11. // business logic ...
  12. return View(p);
  13. }
  14. [HttpGet]
  15. public IActionResult Update()
  16. {
  17. return View();
  18. }
  19. [HttpPost]
  20. public IActionResult Update(Product p)
  21. {
  22. // business logic ...
  23. return View(p);
  24. }
  25. ...
  26. }

PS:Web API 程序支持更全面的 HTTP 方法,详见 Docs