ASP.NET Core 基础(九)——路由Routing
此文是在官方文档的基础上做的个人笔记,一些简单的内容就没用再列出来了,参考官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-5.0
定义:路由是负责匹配传入的http请求,然后进行发送到应用的可执行终结点(代码处理单元)。
本文只介绍较低级别的路由信息,有关MVC中的路由和Razor中的路由参考:
https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0
https://docs.microsoft.com/zh-cn/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-5.0
1. 路由基础知识
路由是一对由UseRouting
和UseEndPoints
注册的中间件:
UseRouting
向管道添加路由配置。此中间件会查看应用中定义的终结点集合,并根据请求选择最佳配置。UseEndpoints
向管道添加终结点执行。它会执行与所选终结点关联的委托。
app.UseRouting();//...app.UseEndpoints(endpoints =>{endpoints.MapGet("/", async context =>{await context.Response.WriteAsync("Hello World!");});});
上述代码MapGet
表示当是get请求时且请求的是根URL时就执行委托。如果请求的方法不是GET或者请求的不是根URL则无路由配置就返回404.
1.1 终结点
MapGet
方法用于定义终结点。终结点可以通过匹配URL和HTTP方法来选择运行委托执行请求。类似MapGet
的方法还有:
Map
、MapDelete
、MapGet
、MapPut
、MapPost
、MapHealthChecks
MapRazorPages
:针对的RazorPageMapController
:针对控制器MapHub<THub>
:针对SingalRMapGrpcService<TService>
:针对gRpc
1.2 路由模板
下面代码的/hello/{name:alpha}
就是一个路由模板,这个模板匹配类似/hello/jim格式的路由。其中alpha叫路由约束,表示name属性应为字母,所以不会匹配/hello/123.
endpoints.MapGet("/hello/{name:alpha}",async c=>
{var name=c.Request.RouteValues["name"];
})
常见路由模板介绍:
路由模板 | 示例匹配 URI | 请求 URI |
---|---|---|
hello | /hello | 仅匹配单个路径 /hello。 |
hello/{name:minlength(4)} |
/hello/jim不匹配 /hello/jimm匹配 |
name最小长度4 |
files/{filename}.{ext?} |
/files/a.txt /files/a |
因为那个点是可选的所以能匹配两个 |
{Page=Home} | / | 匹配并将 Page 设置为 默认值Home。 |
{Page=Home} | /Contact | 匹配并将 Page 设置为 Contact。 |
{controller}/{action}/{id?} | /Products/List | 映射到 Products 控制器和 List 操作。 |
{controller}/{action}/{id?} | /Products/Details/123 | 映射到 Products 控制器和 Details 操作,并将 id 设置为 123。 |
{controller=Home}/{action=Index}/{id?} | / | 映射到 Home 控制器和 Index 方法。 id 将被忽略。 |
{controller=Home}/{action=Index}/{id?} | /Products | 映射到 Products 控制器和 Index 方法。 id 将被忽略。 |
1.3路由约束
路由约束不能等同于输入验证,因为不符合约束的路由直接返回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 生成过程中存在非参数值 |
多个约束的组合 | {id:int:min(1)} | 2 | 整数最小为1 |
路由参数转换 | {path:slugify} | 通过slugify类对path进行转换 |
1.3.1 自定义路由约束
很少情况下需要这么做,当模型绑定也不能实现你的要求时,可以实现IRouteConstraint
接口来创建自定义路由约束
1.4 路由参数转换
使用场景:/subscription-management/get-all
这种的url可以匹配到SubScriptionManagementControll.GetAll
方法上。
首先,定义路由
routes.MapControllerRoute(name: "default",template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
然后写一个类实现IOutboundParameterTransformer
接口:
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();}
}
最后,在startup里进行配置:
public void ConfigureServices(IServiceCollection services)
{services.AddControllers();services.AddRouting(options =>{options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);});
}
当使用Url.Action("GetAll","SubscriptionMangement")
生成路径时,也会生成/subscription-management/get-all
1.5 其它中间件与Routing、EndPoint的关系
//因为还没有调用app.UseRouting,所以这里始终为null
app.Use(next => context =>
{Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");return next(context);
});app.UseRouting();//如果你请求的url匹配到了某个EndPoint,则这里不会返回null
app.Use(next => context =>
{var point=context.GetEndpoint();//可以获取终结点后面跟的元数据进行处理var cls=point?.Metadata.GetMetadata<Class>();Console.WriteLine($"2. Endpoint: {point?.DisplayName ?? "(null)"}");return next(context);
});app.UseEndpoints(endpoints =>
{// 匹配根Url,如果匹配到了,则整个管道到此结束,不会执行后续代码endpoints.MapGet("/", context =>{Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");return Task.CompletedTask;}).WithDisplayName("Hello").WithMetadata(new Class());//带上元数据Class对象
});//如果你请求的url没有匹配到任何EndPoint,才会执行到这里
//所以这里要么不输出,要么输出null
app.Use(next => context =>
{Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");return next(context);
});
- 中间件可以在
UseRouting
前执行,以修改路由操作的数据,如:UseRewriter
,UseHttpMethodOverride
,UsePathBase
. - 中间件可以在
UseRouting
和UseEndPoints
之间运行,以便在执行终结点前处理路由元数据:如:UseAuthorization
、UseCors
。
1.6 路由与主机(Host
)的匹配
使用场景:请求过来的某个url,请求头里的Host字段应符合某个域名规则。如:
- 请求的根路径的主机只能是
contoso.com
或adventure-works.com
。请求健康检查的主机的端口号只能是8080.
public void Configure(IApplicationBuilder app)
{app.UseRouting();app.UseEndpoints(endpoints =>{endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!")).RequireHost("contoso.com");endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!")).RequireHost("adventure-works.com");endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");});
}
- 请求到某个controller上的主机只能是
contoso.com
或adventure-works.com
。但是请求到Privacy这个action上的主机是个例外,只能是example.com:8080
。
[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{public IActionResult Index(){return ControllerContext.MyDisplayRouteInfo();}[Host("example.com:8080")]public IActionResult Privacy(){return ControllerContext.MyDisplayRouteInfo();}
}
1.7路由的性能
当应用出现性能问题的时候,如果开发人员排除了代码逻辑问题,一般都会认为是路由问题。但最常见的根本原因是性能不佳的自定义中间件。以下演示如何测试一个中间件执行耗时:
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{app.Use(next => async context =>{var sw = Stopwatch.StartNew();await next(context);sw.Stop();logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);});app.UseRouting();app.Use(next => async context =>{var sw = Stopwatch.StartNew();await next(context);sw.Stop();logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);});
}
用Time1减去Time2就测出了UseRouting
中间件的性能。上述代码也可以优化如下:
public sealed class MyStopwatch : IDisposable
{ILogger<Startup> _logger;string _message;Stopwatch _sw;public MyStopwatch(ILogger<Startup> logger, string message){_logger = logger;_message = message;_sw = Stopwatch.StartNew();}private bool disposed = false;public void Dispose(){if (!disposed){_logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",_message, _sw.ElapsedMilliseconds);disposed = true;}}
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{int count = 0;app.Use(next => async context =>{using (new MyStopwatch(logger, $"Time {++count}")){await next(context);}});app.UseRouting();app.Use(next => async context =>{using (new MyStopwatch(logger, $"Time {++count}")){await next(context);}});
}
ASP.NET Core 基础(九)——路由Routing相关推荐
- Asp.Net Core EndPoint 终结点路由工作原理解读
Asp.Net Core EndPoint 终点路由工作原理解读 一.背景 在本打算写一篇关于Identityserver4 的文章时候,却发现自己对EndPoint -终结点路由还不是很了解,故暂时 ...
- ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core 项目配置 ( Startup ) - ASP ...
- ASP.NET Core 异常和错误处理 - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core 异常和错误处理 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 异常和错误处理 上一章节中,我们学习了 ASP.NET Cor ...
- ASP.NET Core macOS 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core macOS 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core macOS 环境配置 - ASP.NET Core 基础 ...
- ASP.NET Core Razor 标签助手 - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core Razor 标签助手 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Razor 标签助手 上一章节我们介绍了视图导入,学习了 ...
- ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单 ...
- ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core 动作结果 - ASP.NET Core 基础教程 - 简单教程,简单 ...
- ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程
ASP.NET Core Identity 迁移数据 - ASP.NET Core 基础教程 - 简单教程,简单编程 原文:ASP.NET Core Identity 迁移数据 - ASP.NET C ...
- ASP.NET Core Windows 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core Windows 环境配置 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core Windows 环境配置 ASP.NET Core ...
- ASP.NET Core 基础教程 - ASP.NET Core 基础教程 - 简单教程,简单编程
原文:ASP.NET Core 基础教程 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 是对 ASP.NET 有重大意义的一次重新设计.本章节我们将介绍 A ...
最新文章
- 吴思教授:揭秘视觉信息处理的闭环
- Objective-C:MRC(引用计数器)获得对象所有权的方式(init、retain、copy等)
- 【2037】利用字符串处理,输出x+y的结果
- cuDNN version incompatibility: PyTorch was compiled against 7005 but linked against 6021 问题解决...
- 框架基础——全面解析Java注解
- 【clickhouse】clickhouse 表引擎之 Buffer
- 更新yum源却忘了生成缓存 造成每次启动机器报:the package list needs to be rebuilt...
- 从功能到接口,原来技能可以通用!
- 田间小麦病害自动诊断系统(野外复杂环境)
- MFC异形窗口-多边形窗口-根据图片自定义窗口形状-CRgn
- python木马程序设计_基于HTTP的Python特洛伊木马程序,用于远程系统取证和特权转移
- 干货!软考高级网络规划设计师备考经验分享
- EINT、DINT、ERTM、DRTM和EALLOW、EDIS、ESTOP0解析
- 用计算机弹出晴空,宠物小精灵之晴空之翼
- Linuxptp安装部署
- java使用jacob操作word文档
- 这个儿童节,我们和小时候有什么区别?
- 基于手机蓝牙的arduino遥控小车
- 汇编语言-怎样查看标志位、怎样生成列表文件、列表文件格式
- 基于wasm的探索与研究(四)