1. UseRouting和UseEndpoints方法
1.1作用
路由使用一对由 UseRouting 和 UseEndpoints 注册的中间件:
UseRouting
向中间件管道添加路由匹配。此中间件会查看应用中定义的终结点集,并根据请求选择最佳的终结点匹配。UseEndpoints
向中间件管道添加终结点执行。它会运行与所选终结点关联的委托。
1.2显式调用
应用通常不需要显式调用 UseRouting
或 UseEndpoints
方法。但是,应用可以通过显式调用UseRouting
和 UseEndpoints
方法来更改 UseRouting
和 UseEndpoints
的运行顺序。
app.Use(async (context, next) =>
{
// ...
await next(context);
});
app.UseRouting();
app.MapGet("/", () => "Hello World!");
在上述代码中:
- 对
app.Use
的调用会注册一个在管道的开头运行的自定义中间件。 - 对
UseRouting
的显式调用将路由匹配中间件配置为在自定义中间件之后运行。 - 使用
MapGet
注册的终结点在管道末尾运行。
如果前面的示例不包含对 UseRouting
的显式调用,则 app.Use
自定义中间件将在 UseRouting
路由匹配中间件之后运行。
如果找到匹配项,则 显式调用的UseEndpoints
中间件即为终端中间件,否则继续执行后面的中间件。
1.3终结点元数据
前面的示例中有两个终结点,但只有运行状况检查终结点附加了授权策略。如果请求与运行状况检查终结点 /healthz
匹配,则执行授权检查。这表明,终结点可以附加额外的数据。此额外数据称为终结点元数据:
- 可以通过路由感知中间件来处理元数据。
- 元数据可以是任意的 .NET 类型。
2.路由模板
2.1路由值
例如 /Products/Details/5
,那么路由值为:
{ controller = Products, action = Details, id = 5 }
2.2路由模板
路由参数的格式为{必选路由参数}
或{可选路由参数?}
,比如{controller}
、{action}
、{id?}
。
要匹配路由参数分隔符({
或 }
),请通过重复该字符来转义分隔符( {{
或 }}
)。
//单个路径模板
"hello"
//路由参数模板
"{controller}/{action}/{id?}"
//具有默认值的路由参数模板
"{controller=Home}/{action=Index}/{id?}"
//也可以使用defaults参数定义默认值
app.MapControllerRoute("blog_route", "{controller}/{action}/{id?}",
defaults: new { controller = "Home" });
//查询字符串
//URL 的最后部分 ("?courseID=2021") 是一个查询字符串。
http://localhost:1230/Instructor/Index/1?courseID=2021
//如果将 id 作为查询字符串值传递,模型绑定器也会将 ID 值作为参数传递给 Index 方法
http://localhost:1230/Instructor/Index?id=1&CourseID=2021
Verb | URI | Action |
---|---|---|
[HttpGet] | /Movies | Index |
[HttpGet] | /Movies/Details/id | Details |
[HttpGet] | /Movies/Create | Create |
[HttpPost] | /Movies/Create | Create |
[HttpGet] | /Movies/Edit/id | Edit |
[HttpPost] | /Movies/Edit/id | Edit |
[HttpGet] | /Movies/Delete/id | Delete |
[HttpPost] | /Movies/Delete/id | DeleteConfirmed |
2.3catch-all路由参数
带星号 *
或双星号 **
前缀的路由参数被称为catch-all路由参数。
//假定{ path = "my/path" }
//星号 *
foo/{*path}生成foo/my%2Fpath
//双星号 **
foo/{**path}生成foo/my/path
2.4路由参数内联约束
在路由参数后面添加一个 :
和约束名称可指定路由参数上的内联约束。
[Route("users/{id:int}")]
public User GetUserById(int id) { }
如果约束需要实参,可以在约束名称后添加括号 (...)
的形式提供。
blog/{article:minlength(10)}
通过追加另一个 :
和约束名称,可指定多个内联约束。
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
请勿将约束用于输入验证。如果约束用于输入验证,则无效的输入将导致 404
(找不到页面)响应。路由约束用于消除类似路由的歧义,而不是验证特定路由的输入。
约束 | 示例 | 匹配项示例 | 说明 |
---|---|---|---|
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 生成过程中存在非参数值 |
2.5路由参数constraints约束
//也可以使用constraints参数定义约束
app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
2.6路由参数转换器
ASP.NET Core 框架使用路由参数转化器来转换进行终结点解析的 URI。
与约束类似,可在路由参数名称后面添加 :
和转换器名称。
slug意为一小段,slugify即为slug的形容词。
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
当URI为 /subscription-management/get-all
时,其中的subscription-management
路由参数会被转换为SubscriptionManagementController
控制器,get-all
路由参数会被转换为GetAll
操作,最终URI /subscription-management/get-all
将与 SubscriptionManagementController.GetAll
相匹配。
3.传统(Conventional)路由
传统路由支持使用 MapControllerRoute
、MapAreaControllerRoute
、MapDefaultControllerRoute
终结点。
传统路由放置在Program.cs
文件中。
通常将传统路由用于为浏览器处理 HTML 页面的控制器。
传统路由调用依赖于顺序,前面的会覆盖后面的路由,所以应该最具体的放前面,更通用的放后面。
3.1Map终结点
方法 | 描述 |
Map | 为指定模式匹配 HTTP 请求。 |
MapGet | 为指定模式匹配 HTTP GET 请求。 |
MapPost | 为指定模式匹配 HTTP POST 请求。 |
MapPut | 为指定模式匹配 HTTP PUT 请求。 |
MapDelete | 为指定模式匹配 HTTP DELETE 请求。 |
MapMethods | 为指定方法和模式匹配 HTTP 请求。 |
MapFallback | 为可能优先级最低的非文件名匹配请求。 |
app.MapGet("/", () => "Hello World!");
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
3.2MapControllers终结点
方法 | 描述 |
MapControllers | 不指定任何路由。 |
MapControllerRoute | 使用 name , pattern , defaults , constraints , dataTokens 定义路由。 |
MapDefaultControllerRoute | 添加默认路由 {controller=Home}/{action=Index}/{id?} 。 |
MapAreaControllerRoute | 使用 name , areaName , pattern , defaults , constraints , dataTokens 定义路由。 |
MapDynamicControllerRoute | 尝试使用由TTransformer 生成的路由值选择控制器操作。 |
MapFallbackToController | 为可能优先级最低的非文件名匹配请求,请求将被路由到匹配action , 和 controller 的控制器终结点。 |
MapFallbackToAreaController | 为可能优先级最低的非文件名匹配请求,请求将被路由到匹配action , controller , area 的控制器终结点。 |
public static Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder MapControllerRoute (this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string name, string pattern, object? defaults = default, object? constraints = default, object? dataTokens = default);
//默认(default)传统路由
//按位置传递参数
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
//按命名传递参数
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
//简写
app.MapDefaultControllerRoute();
//专用(dedicated)传统路由
app.MapControllerRoute(
name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
3.3MapHealthChecks终结点
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
MapHealthChecks 调用添加运行状况检查终结点。 将 RequireAuthorization 链接到此调用会将授权策略附加到该终结点。
4.特性(Attribute)路由
特性路由支持使用 MapControllerRoute
、MapAreaControllerRoute
、MapControllers
终结点。
特性路由支持放置在控制器(controller)或操作(action)上。
通常将特性路由用于处理 REST API 的控制器。
特性路由调用不依赖于顺序,前面的不会覆盖后面的路由,所以路由全部执行,但是最具体的路由有机会在更通用的路由之前执行。
不能通过传统路由访问定义属性路由的操作,反之亦然。
4.1[Route]特性
[Route]
特性虽然可用在控制器和操作上,但一般只用在控制器上,用于指定此控制器中的所有操作的路由前缀。
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
4.2[Http]特性
Http特性:[HttpGet]
、[HttpPost]
、[HttpPut]
、[HttpPatch]
、[HttpDelete]
、[HttpHead]
。
Http特性只可以用在操作上。
[HttpGet]
[HttpGet("int/{id:int}")]
[HttpGet("/products2/{id}", Name = "Products_List")]
应用于操作的以 /
或 ~/
开头的路由模板不与应用于控制器的路由模板合并。
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
4.3标记替换
特性路由支持标记替换,方法是将标记用方括号([
、]
)括起来。
标记 [action]
、[area]
和 [controller]
替换为定义了路由的操作中的操作名称值、区域名称值和控制器名称值。
要匹配标记替换分隔符 [
或 ]
,可通过重复该字符([[
或 ]]
)对其进行转义。
[Route("[controller]/[action]")]
[Route("[controller]/[action]", Name="[controller]_[action]")]
5.区域(Area)路由
5.1通过 Visual Studio 添加区域
在解决方案资源管理器中,右键单击项目名称,然后选择添加
–新搭建基架的项目
–MVC 区域
,输入区域名称
即可。
5.2添加区域路由
public static Microsoft.AspNetCore.Builder.ControllerActionEndpointConventionBuilder MapAreaControllerRoute (this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string name, string areaName, string pattern, object? defaults = default, object? constraints = default, object? dataTokens = default);
//区域路由
//按位置传递参数
app.MapAreaControllerRoute("MyAreaProducts", "Products", "Products/{controller=Home}/{action=Index}/{id?}");
app.MapAreaControllerRoute("MyAreaServices", "Services", "Services/{controller=Home}/{action=Index}/{id?}");
//按命名传递参数
app.MapAreaControllerRoute(
name: "MyAreaProducts",
areaName: "Products",
pattern: "Products/{controller=Home}/{action=Index}/{id?}");
app.MapAreaControllerRoute(
name: "MyAreaServices",
areaName: "Services",
pattern: "Services/{controller=Home}/{action=Index}/{id?}");
//等同于
app.MapControllerRoute("MyAreaProducts", "Products/{controller=Home}/{action=Index}/{id?}",
defaults: new { area = "Products" }, constraints: new { area = "Products" });
app.MapControllerRoute("MyAreaServices", "Services/{controller=Home}/{action=Index}/{id?}",
defaults: new { area = "Services" }, constraints: new { area = "Services" });
//如果所有控制器都在同一个区域,则可以使用{area:exists}约束
app.MapControllerRoute(
name: "blog_route",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
5.3文件夹结构
- Project name
- Areas
- Products
- Controllers
- HomeController.cs
- ManageController.cs
- Views
- Home
- Index.cshtml
- Manage
- Index.cshtml
- About.cshtml
- Home
- Controllers
- Services
- Controllers
- HomeController.cs
- Views
- Home
- Index.cshtml
- Home
- Controllers
- Products
- Areas
5.4将控制器与区域关联
using Microsoft.AspNetCore.Mvc;
using Microsoft.Docs.Samples;
namespace MVCareas.Areas.Products.Controllers;
[Area("Products")]
public class ManageController : Controller
{
public IActionResult Index()
{
ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
return View();
}
public IActionResult About()
{
ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
return View();
}
}
原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/csharp/aspnetcoremvc/14310.html