.NetCore 路由配置指南
ASP.NET Core 控制器使用路由中间件来匹配传入请求的 URL 并将它们映射到操作。本文以使用者的角度,对路由的使用进行概括说明,方便知识回顾与使用。
前言
ASP.NET Core 控制器使用路由中间件来匹配传入请求的 URL 并将它们映射到操作。它支持传统路由,也支持属性路由。如果感觉到陌生,不要着急,继续向下看,下面会一一道来。
传统路由
传统路由通常在 MVC 框架中使用。
Program.cs 配置
它在 program.cs
中定义,如下:
完整方法:
1 | app.MapControllerRoute( |
简化使用:
1 | app.MapDefaultControllerRoute(); |
说明:
上面的完整路由定义中:
第一个路径段
{controller=Home}
映射到控制器名称。如
UserController
中的控制器名为User
。第二段
{action=Index}
映射到操作名称。action
就是 Controller 类中的方法名。第三段
{id?}
用于可选id
。{id?}
中的?
使其成为可选。id
用于映射到模型实体。
Controller 定义
1 | public class HomeController : Controller |
多个传统路由
可以多次调用 MapControllerRoute
来设置多个传统路由,如下:
1 | app.MapControllerRoute(name: "blog", |
上述代码中的 blog 路由是专用的传统路由。 之所以称为专用传统路由是因为
controller 和 action 不会以参数形式出现在路由模板 "blog/{*article}"
中,它们只能具有默认值 { controller = "Blog", action = "Article"
}。因此,此路由将会始终映射到操作
BlogController.Article
。
传统路由顺序
按定义顺序匹配
具体的路由在可变路由之前匹配
比如
users/demo
会在users/{userId}
之前进行匹配
特性(Attribute)路由
Attribute 本应翻译成属性,但为了与 .NET 中的属性字段区分,本文称之为特性。
特性路由通常在 REST API 中使用。
Program.cs 配置
1 | var builder = WebApplication.CreateBuilder(args); |
属性路由通过调用 MapControllers
来映射属性路由控制器。
Controller 定义
下面的示例中,HomeController
匹配一组类似于默认传统路由
{controller=Home}/{action=Index}/{id?}
匹配的 URL。
1 | public class HomeController : Controller |
保留关键字
action
area
controller
handler
page
这些关键词是保留的路由参数名,在定义路由时,不能使用这些关键词。
HTTP 谓词模板
路由模板
路由模板用于定义路由匹配的模板,它分为
谓词模板示例
假设如下控制器:
1 | [ ] |
在上述代码中:
每个操作都包含
[HttpGet]
属性,该属性仅将匹配限制为 HTTP GET 请求。GetProduct
操作包含"{id}"
模板,因此id
被附加到控制器上的"api/[controller]"
模板中。 方法模板为"api/[controller]/"{id}""
。 因此,此操作仅匹配/api/test2/xyz
、/api/test2/123
、/api/test2/{any string}
等形式的 GET 请求。1
2
3
4
5[// GET /api/test2/xyz ]
public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}GetIntProduct
操作包含"int/{id:int}")
模板。 模板的:int
部分将id
路由值限制为可以转换为整数的字符串。1
2
3
4
5[// GET /api/test2/int/3 ]
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}对于
/api/test2/int/abc
的 GET 请求,将会无法匹配到路由,并返回404 Not Found
错误GetInt2Product
操作在模板中包含{id}
,但不将id
限制为可以转换为整数的值。 对于/api/test2/int2/abc
的 GET 请求,处理如下:与此路由匹配。
模型绑定无法将
abc
转换为整数。 该方法的id
参数是整数。返回 400 Bad Request,因为模型绑定未能将
abc
转换为整数。
1
2
3
4
5[// GET /api/test2/int2/3 ]
public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
生成 REST API 时,很少需要在 操作方法
上使用
[Route(...)]
,因为该操作接受所有 HTTP 方法。
建议使用更具体的 HTTP 谓词属性来明确 API 所支持的操作。 API 的 REST
客户端应知道哪些路径和 HTTP 谓词映射到特定的逻辑操作。
REST API 应使用属性路由将应用的功能建模为一组资源,其中操作由 HTTP 谓词表示。 也就是说,对同一逻辑资源执行的许多操作(例如,GET 和 POST)都使用相同 URL。
1 | [ ] |
上述代码中的 URL 路径为 /products3
:
- 当 HTTP
谓词 为
GET
时,调用MyProductsController.ListProducts
。 - 当 HTTP
谓词 为
POST
时,调用MyProductsController.CreateProduct
。
特性路由组合
在控制器上定义的所有路由模板均作为操作上路由模板的前缀。在控制器上放置的路由特性会使控制器中的所有操作都使用该特性路由。
1 | [ ] |
在上面的示例中:
- URL 路径
/products
可以匹配ProductsApi.ListProducts
- URL 路径
/products/5
可以匹配ProductsApi.GetProduct(int)
。
这两项操作仅匹配 HTTP GET
,因为它们标记了
[HttpGet]
。
操作上以 /
或 ~/
开头的路由模板不与控制器的路由模板合并。
Attribute | 与 [Route("Home")] 结合 |
定义路由模板 |
---|---|---|
[Route("")] |
是 | "Home" |
[Route("Index")] |
是 | "Home/Index" |
[Route("/")] |
否 | "" |
[Route("About")] |
是 | "Home/About" |
特性路由继承
在父类控制器上定义的路由特性会继承给子类,可以在父类中定义一个通用的路由特性,减少在子类的控制器上重复定义。
例如:
1 | [ ] |
标记替换
特性路由支持标记替换,将标记用方括号([
、]
)括起来即可。
标记 [action]
、[area]
和
[controller]
会替换成定义了路由的操作中的操作名称、区域名称和控制器名称。
1 | // Products0:控制器名称 |
标记样式转换
[controller]
,[action]
等会默认使用定义的名称作用 URL,而在实际开发中,我们可能需要将
PascalCase 命名转换成 hyphenCase 命名,如将 FindAll
变成
find-all
。
可以通过实现 IOutboundParameterTransformer 接口来自定义。
接口实现:
1 | using System.Text.RegularExpressions; |
使用:
1 | builder.Services.AddControllersWithViews(options => |
RouteTokenTransformerConvention 是应用程序的模型约定,可以:
将参数转换程序应用到程序中的所有特性路由中。
在替换特性路由标记值时对其进行自定义
多个路由特性
同一个控制器或者路由上,可以同时添加多个路由特性标记。
1 | [ ] |
一般不要使用多个路由特性,会让 URL 看起来不易于理解,且容易冲突。
可选参数、默认值和约束
特性路由支持使用与传统路由相同的内联语法,来指定可选参数、默认值和约束。
1 | public class Products14Controller : Controller |
使用说明:
=
赋予默认值:
进行约束,可以同时使用多个约束?
表示可选参数
内置路由约束:
约束 | 示例 | 匹配项示例 | 说明 |
---|---|---|---|
int |
{id:int} |
123456789 ,
-123456789 |
匹配任何整数 |
bool |
{active:bool} |
true , FALSE |
匹配 true 或
false 。 不区分大小写 |
datetime |
{dob:datetime} |
2016-12-31 ,
2016-12-31 7:32pm |
在固定区域性中匹配有效的
DateTime 值。 请参阅前面的警告。 |
decimal |
{price:decimal} |
49.99 ,
-1,000.01 |
在固定区域性中匹配有效的
decimal 值。 请参阅前面的警告。 |
double |
{weight:double} |
1.234 ,
-1,001.01e8 |
在固定区域性中匹配有效的
double 值。 请参阅前面的警告。 |
float |
{weight:float} |
1.234 ,
-1,001.01e8 |
在固定区域性中匹配有效的
float 值。 请参阅前面的警告。 |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 |
匹配有效的 Guid 值 |
long |
{ticks:long} |
123456789 ,
-123456789 |
匹配有效的 long 值 |
minlength(value) |
{username:minlength(4)} |
Rick |
字符串必须至少为 4 个字符 |
maxlength(value) |
{filename:maxlength(8)} |
MyFile |
字符串不得超过 8 个字符 |
length(length) |
{filename:length(12)} |
somefile.txt |
字符串必须正好为 12 个字符 |
length(min,max) |
{filename:length(8,16)} |
somefile.txt |
字符串必须至少为 8 个字符,且不得超过 16 个字符 |
min(value) |
{age:min(18)} |
19 |
整数值必须至少为 18 |
max(value) |
{age:max(120)} |
91 |
整数值不得超过 120 |
range(min,max) |
{age:range(18,120)} |
91 |
整数值必须至少为 18,且不得超过 120 |
alpha |
{name:alpha} |
Rick |
字符串必须由一个或多个字母字符组成,a -z ,并区分大小写。 |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 |
字符串必须与正则表达式匹配。 请参阅有关定义正则表达式的提示。 |
required |
{name:required} |
Rick |
用于强制在 URL 生成过程中存在非参数值 |
自定义特性路由
所有路由属性都实现 IRouteTemplateProvider
。 ASP.NET Core
运行时:
- 应用启动时,在控制器类和操作方法上查找属性。
- 使用实现
IRouteTemplateProvider
的属性来构建初始路由集。
每个 IRouteTemplateProvider
都允许定义一个包含自定义路由模板、顺序和名称的路由:
1 | public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider |
上述 Get
方法返回
Order = 2, Template = api/MyTestApi
。
路由返回值
ASP.NET Core 使用以下类型作为 Web API 控制器的操作返回类型:
请点击 ASP.NET Core Web API 中控制器操作的返回类型 进行详细阅读
传统路由与特性路由对比
类型 | 传统路由 | 特性路由 |
---|---|---|
定义方式 | 在 Program.cs 中调用 MapControllerRoute
建立 URL 映射 |
在每个 Controller 中通过特性来定义 URL 映射 |
操作性 | 更简洁 | 要对每个 action 进行定义 |
路由快速配置
当理解了路由相关知识后,需要可以快速应用到实际项目中,本节记录一些快速配置代码,方便进行初始化。
映射路由
1 | // ... |
设置 hyphenCase 路由
增加 SlugifyParameterTransformer
类
1 | using System.Text.RegularExpressions; |
Program.cs
中配置
1 | builder.Services.AddControllersWithViews(options => |
新建路由基类
所有子类都继承自这个基类
1 | [ ] |
参考
在 ASP.NET Core 中路由到控制器操作 | Microsoft Learn
ASP.NET Core 中的路由 | Microsoft Learn