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路由的更多相关文章

  1. (8)ASP.NET Core 中的MVC路由一

    1.前言 ASP.NET Core MVC使用路由中间件来匹配传入请求的URL并将它们映射到操作(Action方法).路由在启动代码(Startup.Configure方法)或属性(Controlle ...

  2. (9)ASP.NET Core 中的MVC路由二

    1.URL生成 MVC应用程序可以使用路由的URL生成功能,生成指向操作(Action)的URL链接. IUrlHelper 接口用于生成URL,是MVC与路由之间的基础部分.在控制器.视图和视图组件 ...

  3. asp.net core 系列 5 MVC框架路由(上)

    一. 概述 介绍asp.net core路由时,我初步想了下,分几篇来说明.  路由的知识点很多,参考了官方文档提取出一些重要的知识点来说.    在ASP.NET Core中是使用路由中间件来匹配传 ...

  4. ASP.Net Core 2.2 MVC入门到基本使用系列 (三)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  5. 在 Asp.Net Core 中安装 MVC

    在 ASP.NET Core 中安装 MVC 到目前为止,我们在本系列视频中使用的 ASP.NET Core 项目是使用“空”项目模板生成的.目前这个项目没有设置和安装 MVC. 两个步骤学会在 AS ...

  6. 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 ...

  7. ASP.NET Core 2.0 MVC项目实战

    一.前言 毕业后入职现在的公司快有一个月了,公司主要的产品用的是C/S架构,再加上自己现在还在学习维护很老的delphi项目,还是有很多不情愿的.之前实习时主要是做.NET的B/S架构的项目,主要还是 ...

  8. ASP.Net Core 2.2 MVC入门到基本使用系列 (二)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  9. ASP.Net Core 2.2 MVC入门到基本使用系列 (一)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  10. ASP.Net Core 2.2 MVC入门到基本使用系列 (四)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

随机推荐

  1. golang中的锁竞争问题

    索引:https://www.waterflow.link/articles/1666884810643 当我们打印错误的时候使用锁可能会带来意想不到的结果. 我们看下面的例子: package ma ...

  2. Silky微服务框架之模块

    模块的定义 Silky是一个包括多个nuget包构成的模块化的框架,每个模块将程序划分为一个个小的结构,在这个结构中有着自己的逻辑代码和自己的作用域,不会影响到其他的结构. 模块类 一般地,一个模块的 ...

  3. 五、kubernetes节点与令牌管理

    Kubernetes节点与令牌管理 一.令牌管理 查看令牌 [root@master ~]# kubeadm token list 删除令牌 [root@master ~]# kubeadm toke ...

  4. ajax-Xhr

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. UML建模语言、设计原则、设计模式

    1.UML统一建模语言 定义:用于软件系统设计与分析的语言工具 目的:帮助开发人员更好的梳理逻辑.思路 学习地址:UML概述_w3cschool 官网:https://www.omg.org/spec ...

  6. 版本控制工具Git介绍-01

    使用版本控制工具是为了方便团队开发,比如多人共同维护一个项目的时候,用版本控制工具可以很方便的维护项目代码,如果哪天你改了一个版本,出问题了,我们也可以很快的找到你改了什么,这里介绍使用比较多的版本控 ...

  7. maple软件安装教程

    Maple2022适用于Win7/10/11(64位)系统,亲测可用! Maple2022 WIN10 64位安装步骤:1.先使用"百度网盘"下载MPE22_CN_x64安装包到电 ...

  8. HDLBits答案——Verification: Writing Testbenches

    1 clock module top_module ( ); reg clk; dut U1(.clk(clk)); initial begin clk = 0; end always begin # ...

  9. Day34.2:Calendar详解

    Calendar 1.1 概述 Date类中很多方法被Calendar所取代,Calendar类提供了获取和设置各种日历的方法. 1.2 方法 构造方法:Calendar类的构造器被protected ...

  10. VideoPipe可视化视频结构化框架更新总结(2022-12-9)

    项目地址:https://github.com/sherlockchou86/video_pipe_c 往期文章:https://www.cnblogs.com/xiaozhi_5638/p/1685 ...