【ASP.NET Core】动态映射MVC路由
ASP.NET Core 中的几大功能模块(Razor Pages、MVC、SignalR/Blazor、Mini-API 等等)都以终结点(End Point)的方式公开。在HTTP管道上调用时,其扩展方法基本是以 Map 开头,如 MapControllers、MapBlazorHub。
对于 MVC 应用,常用的是静态路由匹配方式,即调用以下方法:
MapControllers
MapControllerRoute
MapDefaultControllerRoute
MapAreaControllerRoute
它们的特点是路由模板是固定的——提供 controller、action 或 area 等关键字段的值,如咱们严重熟悉的 {controller=Home}/{action=Index}/{id?}。在访问控制器时,必须按照路由规的格式提供相应的值。比如,访问 DouFuZha 控制器下的 Boom 操作,则需要URL:/doufuzha/boom。也就是说:控制器名称和操作名称都是直接指定的,没有中途转换。
相反地,如果 controller、action 等关键字段不直接提供,或者需要翻译(转换),这就涉及到调用 MapDynamicControllerRoute 扩展方法的事了。这个方法不要翻译为“动态MVC”,这样翻译会被误解为“运行时动态产生控制器”(其实真有高人这么做,综合运用代码生成和动态编译生成控制器,但实际开发中比较少用到,除非有特殊需求的庞大系统)。这里的“动态”修饰的是路由,所以,这个扩展方法是允许开发者使用另一个路由规则来动态映射相应的控制器/操作。
这个有什么用途呢?既然有这个功能,当然是有用的。例如
【情况一】(这是很多教程文章都用的例子)控制器名:Cats,操作:Play。
指定动态路由:{lang}/{controller}/{action}
其中,lang 表示语言,这样就可以不同语言使用不同的 URL 了。请看:
中文:zh/猫/撸猫
英文:en/cats/play
于是,应用程序在运行阶段动态转译,先提取 lang 字段的值,看看是什么语言,如果是 zh ,那么 controller=猫 要转换为 controller = cats;action=撸猫 要转换为 action=play。如果 lang 字段的值是 en,可以不转换。
【情况二】控制器有两个:MembersV1、MembersV2
指定动态路由:{controller}/{action}/{v}
如果 controller=members,v=1,那么,转换为:controller=MembersV1,action 的值不变。
如果 controller=members,v=2,那么,转换为:controller=MembersV2,action 的值不变。
如果 controller != members,不做任何转换。
【情况三】比较奇葩,动态路由中不包含控制器名,只包含操作名称。
{action}
控制器名称通过 HTTP 请求的头部来提供。
GET /cooking
accept: ...
host: ...
controller: dabaicai
于是,经过转换,得到 controller=DaBaiCai,action=Cooking(煮大白菜)。
上面只是列举了一些情况,其实还有很多场景是可以用到动态路由 MVC 的。
下面咱们聊聊怎么去运用。实现 MVC 的动态路由需要知道一个核心类—— DynamicRouteValueTransformer。这是个抽象类,需要实现抽象方法 TransformAsync。该方法是异步等待的,签名如下:
public abstract ValueTask<RouteValueDictionary> TransformAsync (HttpContext httpContext, RouteValueDictionary values);
你会发现一件有意思的事:输入参数 values 和返回值都是路由规则的数据字典。估计你也看出这货的思路了,是的,输入参数的 values 动态路由模板被匹配后产生的字段集,而返回的字典是你根据实际需求转换后的路由字段集。
如前文举例的 {controller}/{action}/{v},如果访问:http://abc.com/members/register/2,那么,values 参数包含的数据为:
controller: members
action: register
v: 2
members 控制器不存在的,所以,根据 v 的值进行转换,最终返回的字典数据为:
controller: MembersV2
action: Register
你也会发现,TransformAsync 方法还有个 HttpContext 参数。对的,这是为了方便你分析 HTTP 请求消息用的。比如,前文的举例中,就有个脑洞大开的,把控制器名藏在 Header 里面。这时候就可以通过这个 HttpContext 参数访问 Headers 集合,把 HTTP 头的值读出来,并作为 controller 路由字段的值。
-----------------------------------------------------------------------------------------------------------------------
接下来,我们新手试玩。
老周这个示例是固定路由和动态路由同时使用的,这样比较实用。好,咱先不说太多。来看看控制器的代码。
public class HomeController : Controller
{
public ActionResult Main()
{
var context =HttpContext.RequestServices.GetRequiredService<StudentsDbContext>();
return View("MainView", context.Students.ToArray());
} public ActionResult NewStudent() => View(); public ActionResult AddNewItem(Student stu)
{
var dbcontext = HttpContext.RequestServices.GetRequiredService<StudentsDbContext>();
if (ModelState.IsValid)
{
dbcontext.Students.Add(stu); dbcontext.SaveChanges();
}
return RedirectToAction("Main");
} public ActionResult DeleteItem(long sid)
{
var context = HttpContext.RequestServices.GetRequiredService<StudentsDbContext>();
var q = from s in context.Students
where s.Id == sid
select s;
if(q.Count() == 1)
{
Student? _stu = q.FirstOrDefault();
if(_stu != null)
{
context.Students.Remove(_stu);
context.SaveChanges();
}
}
return RedirectToAction("Main");
}
}
这个控制器只做演示,所以比较简单,主要是 Main 浏览学生信息;NewStudent 展示新增学生信息的页面,AddNewItem 在 <form> 元素 POST 时调用,向数据库插入一条学生信息;DeleteItem 根据学生 ID 删除一条学生数据。
下面是 MainView 视图,它主要浏览学生信息。
@model IEnumerable<Student> <div>
<a asp-controller="Home" asp-action="NewStudent">新增</a>
</div> <div>
@if(Model.Count() == 0)
{
<p>什么鬼都没有</p>
}
else
{
<table style="width:85%;margin-top:15px;" border="1" cellpadding="2" cellspacing="0">
<thead>
<tr>
<th>编号</th>
<th>姓名</th>
<th>电邮</th>
<th>年龄</th>
</tr>
</thead>
<tbody>
@foreach(var student in Model)
{
<tr>
<td>@student.Id</td>
<td>@student.Name</td>
<td>@student.Email</td>
<td>@student.Age</td>
<td><a href="/delone/@student.Id">删除</a></td>
</tr>
}
</tbody>
</table>
}
</div>
这个页面里已经混合了固定路由和动态路由了。
1、asp-controller、asp-action 是标记帮助器(Tag Helpers)实现的,指定要访问的控制器和操作方法,会为 <a> 元素自动生成链接,这是固定路由,生成的URL的路由模板是我们熟悉的:{controller}/{action}。
2、在每一行数据的“删除”链接上,/delone/@student.Id 是动态路由,@student.Id是返回ID的值,即 /delone/1、/delone/2、/delone/6 等。这个用的是动态路由:{op}/{sid:long?},匹配后,op=delone,sid=1,sid=2……
还有,控制器代码中,AddNewItem 和 DeleteItem 操作方法在处理完后跳转回 Main 操作方法时,也是使用了固定路由。
return RedirectToAction("Main");
下面就是核心部分了,实现 DynamicRouteValueTransformer 抽象类。
public class CustTransform : DynamicRouteValueTransformer
{
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
// 这个动态路由要有 op 字段
if (!values.ContainsKey("op"))
{
return new ValueTask<RouteValueDictionary>(values);
}
var newValues = new RouteValueDictionary();
string? k = values["op"] as string;
if (k == null || k is { Length: 0 })
{
return new ValueTask<RouteValueDictionary>(values);
}
// 转换路由参数
switch (k.ToLowerInvariant())
{
case "addone":
newValues["controller"] = "Home";
newValues["action"] = "NewStudent";
break;
case "listall":
newValues["controller"] = "Home";
newValues["action"] = "Main";
break;
case "delone":
newValues["controller"] = "Home";
newValues["action"] = "DeleteItem";
// 解析id
if(values.TryGetValue("sid", out object? val) && val != null)
{
newValues["sid"] = val;
}
break;
default:
newValues["controller"] = "Home";
newValues["action"] = "Main";
break;
}
return new ValueTask<RouteValueDictionary>(newValues);
}
}
有许多教程的示例代码是直接修改 values 然后将它返回,这样会增加了代码对 values 实例的引用,听说这样会导致内存泄漏。老周的代码是 new 一个新的 RouteValueDictionary 实例,如果要要修改,就把它返回;如果不需要修改,可以把 values 直接返回。至于这样会不会有问题,不太好说,反正目前来说能正常运行,内存占用没多大变化。
这里的转换思路是这样的:
动态路由 {op}/{sid?},sid 可选,在删除数据时要用。
如果 op=addone ==> controller=Home,action=NewStudent;
如果 op=listall ==> controller=Home,action=Main;
如果 op=delone,sid需要有值 ==> controller=Home,action=DeleteItem,sid=sid(这个sid从动态路由传过来)。
如果 op=其他值,直接 controller=Home,action=Main。
现在,你明白前面视图文件中,“删除”链接的 href 为啥是 /delone/@student.Id 了吧。
CustTransform 类要注册到服务容器中,因为动态路由在执行时是从服务容器获取 DynamicRouteValueTransformer 实例的。
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<CustTransform>();
这里我就直接注册为单实例模式,反正不需要反复实例化。
在应用程序 Build 了后,需要映射终结点。
var builder = WebApplication.CreateBuilder(args);
…… var app = builder.Build(); app.MapControllerRoute("app", "{controller}/{action}/{sid:long?}");
app.MapDynamicControllerRoute<CustTransform>("{op=main}/{sid:long?}"); app.Run();
以前看到网上有人问:为什么我用了动态路由之后,asp-controller、asp-action 等 Tag Helper 不能用了?就是因为你只调用了 MapDynamicControllerRoute 方法,而忘了调用 MapControllerRoute 方法。
如果你同时用到了固定路由和动态路由,一定要同时调用两个方法。放心,它们不会冲突,除非你指定的路由模板重复。如果你整个项目都用的是动态路由,那可以不调用 MapControllerRoute 方法。
这个示例老周图省事,用的是内存数据库(In-Memory DB)。这是 DB 上下文和模型类。
// 实体
[PrimaryKey(nameof(Id))]
public class Student
{
public long Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public int? Age { get; set; }
} // 数据库上下文
public class StudentsDbContext : DbContext
{
public StudentsDbContext(DbContextOptions<StudentsDbContext> options) : base(options) { } public DbSet<Student> Students => Set<Student>();
}
Id 属性是主键。
运行之后,咱们看看生成的 HTML。


内存数据库只存在内存中,所以每次运行后,要手动添加一些数据来测试。
这样,固定路由和动态路由的URL都能同时工作了。
【ASP.NET Core】动态映射MVC路由的更多相关文章
- (8)ASP.NET Core 中的MVC路由一
1.前言 ASP.NET Core MVC使用路由中间件来匹配传入请求的URL并将它们映射到操作(Action方法).路由在启动代码(Startup.Configure方法)或属性(Controlle ...
- (9)ASP.NET Core 中的MVC路由二
1.URL生成 MVC应用程序可以使用路由的URL生成功能,生成指向操作(Action)的URL链接. IUrlHelper 接口用于生成URL,是MVC与路由之间的基础部分.在控制器.视图和视图组件 ...
- asp.net core 系列 5 MVC框架路由(上)
一. 概述 介绍asp.net core路由时,我初步想了下,分几篇来说明. 路由的知识点很多,参考了官方文档提取出一些重要的知识点来说. 在ASP.NET Core中是使用路由中间件来匹配传 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (三)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- 在 Asp.Net Core 中安装 MVC
在 ASP.NET Core 中安装 MVC 到目前为止,我们在本系列视频中使用的 ASP.NET Core 项目是使用“空”项目模板生成的.目前这个项目没有设置和安装 MVC. 两个步骤学会在 AS ...
- ASP.NET CORE 1.0 MVC API 文档用 SWASHBUCKLE SWAGGER实现
from:https://damienbod.com/2015/12/13/asp-net-5-mvc-6-api-documentation-using-swagger/ 代码生成工具: https ...
- ASP.NET Core 2.0 MVC项目实战
一.前言 毕业后入职现在的公司快有一个月了,公司主要的产品用的是C/S架构,再加上自己现在还在学习维护很老的delphi项目,还是有很多不情愿的.之前实习时主要是做.NET的B/S架构的项目,主要还是 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (二)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (一)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (四)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
随机推荐
- redis位图(bitmap)常用命令的解析
描述 bitmap是redis封装的用于针对位(bit)的操作,其特点是计算效率高,占用空间少,常被用来统计用户签到.登录等场景 常用命令及解析 常用命令 setbit key offset va ...
- 使用@Transactional注解的方法所在的类获取不到注解的解决方案
前段时间遇到一个问题,一个service叫做A吧,有多个实现类分别是B,C,D,需要根据前端传的不同参数去匹配不同的实现类,我就自定义了一个注解@OrderDeal放在B,C,D上面,然后匹配前端传的 ...
- 图学习【参考资料2】-知识补充与node2vec代码注解
本项目参考: https://aistudio.baidu.com/aistudio/projectdetail/5012408?contributionType=1 *一.正题篇:DeepWalk. ...
- 高精度加法(Java)
题目描述 高精度加法,相当于 a+b problem,不用考虑负数. 输入格式 分两行输入. a , b ≤ 10^500 输出格式 输出只有一行,代表 a + b 的值. 思路 使用数组进行模拟, ...
- 1B踩坑大王
题目链接 题目大意: 人们常用的电子表格软件(比如: Excel)采用如下所述的坐标系统: 第一列被标为 A,第二列为 B,以此类推,第 262626 列为 Z.接下来为由两个字母构成的列号: 第 2 ...
- 2022春每日一题:Day 24
题目:Work Group 树形dp,设状态f[u][0/1] 表示以u为根节点,他的子树中选了0(偶数)1(奇数)个节点的最大价值,设x为他的一个儿子,显然f[u][1]=max(f[k][0]+f ...
- 轻量级领域驱动设计DDD Lite在嵌入式系统重构中的应用
前言 目前,关于领域驱动设计(Domain Driven Design)DDD的培训,材料,视频都比较多,大家对DDD的一些概念都有所了解,但是在实际使用过程中,有很多的问题.例如 为什么DDD的架构 ...
- docker中php xdebug调试开发
docker-compose环境来自:https://github.com/zhaojunlik...原文:http://blog.oeynet.com/post/9... 说明 在开发中,断点调试是 ...
- chronyd为隔离网络设置时间同步
参考链接:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_basic ...
- 关于linux上mysql导出excel 文件
这里简单介绍两种方法导出 1.在mysql交互中 首先查看"secure_file_priv"变量 SHOW VARIABLES LIKE "secure_file_pr ...