【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都能同时工作了。

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/tcjiaan/p/17072356.html

如何使用masa.blazor-编程思维

MASA.Blazor 是什么? 基于Material Design设计和BlazorComponent的交互能力提供标准的基础组件库。提供如布局、弹框标准、Loading、全局异常处理等标准场景的预置组件。从更多实际场景出发,满足更多用户和场景的需求,缩短开发周期,提高开发效率,并提供一整套Web解决方案 - MAS

基于 .net7.0 开发telegram 机器人(入门)-编程思维

简介 Telegram(非正式简称TG、电报)是跨平台的即时通信软件,其客户端是自由及开放源代码软件,但服务端是专有软件。用户可以相互交换加密与自毁消息,发送照片、视频等所有类型文件。官方提供手机版(Android、iOS、Windows Phone)、桌面版(Windows、macOS、Linux)和网页版[8]等

推荐一款在浏览器编辑`blazor`的`ide`-编程思维

不知道是否有Blazor用户羡慕过React或者Vue用户,在一些组件库中,它们就提供了在当前的组件预览对于组件的实时编辑并且预览? 比如semi-design的这种 在比如codepen这种 由于Blazor的诞生时间对比现有的前端来说太短了,以至于这种在线编译的基本没有, 有时候在写界面的时候调试也是一种痛苦,

blazor hybrid (blazor混合开发)更好的读取本地图片-编程思维

在 Blazor Hybrid 应用中,Razor 组件在设备上本机运行。 组件通过本地互操作通道呈现到嵌入式 Web View 控件。 组件不在浏览器中运行,并且不涉及 WebAssembly。 Razor 组件可快速加载和执行代码,组件可通过 .NET 平台完全访问设备的本机功能。 Web View 中呈现的组件样

分布式事务 | 使用 dotnetcore/cap 的本地消息表模式-编程思维

本地消息表模式 本地消息表模式,其作为柔性事务的一种,核心是将一个分布式事务拆分为多个本地事务,事务之间通过事件消息衔接,事件消息和上个事务共用一个本地事务存储到本地消息表,再通过定时任务轮询本地消息表进行消息投递,下游业务订阅消息进行消费,本质上是依靠消息的重试机制达到最终一致性。其示意图如下所示,主要分为以下三步:

如何将webassembly优化到1mb?-编程思维

Blazor WebAssembly加载优化方案 对于Blazor WebAssembly加载方案的优化是针对于WebAssembly首次加载,由于BlazorWebAssembly是在首次加载的时候会将.NET Core的所有程序集都会加载到浏览器中,并且在使用的时候可能引用了很多第三方的dll,导致加载缓慢; 优化

【asp.net core】用配置文件来设置授权角色-编程思维

在开始之前,老周先祝各个次元的伙伴们新春快乐、生活愉快、万事如意。 在上一篇水文中,老周介绍了角色授权的一些内容。本篇咱们来聊一个比较实际的问题——把用于授权的角色名称放到外部配置,不要硬编码,以方便后期修改。 由于要配置的东西比较简单,咱们并不需要存在数据库,而是用 JSON 文件配置就可以了。将授权策略和角色列表关

【asp.net core】按用户角色授权-编程思维

上次老周和大伙伴们分享了有关按用户Level授权的技巧,本文咱们聊聊以用户角色来授权的事。 按用户角色授权其实更好弄,毕竟这个功能是内部集成的,多数场景下我们不需要扩展,不用自己写处理代码。从功能语义上说,授权分为按角色授权和按策略授权,而从代码本质上说,角色权授其实是包含在策略授权内的。怎么说呢?往下看。 角色授权主

【asp.net core】按用户等级授权-编程思维

验证和授权是两个独立但又存在联系的过程。验证是检查访问者的合法性,授权是校验访问者有没有权限查看资源。它们之间的联系——先验证再授权。 贯穿这两过程的是叫 Claim 的东东,可以叫它“声明”。没什么神秘的,就是由两个字符串组成的对象,一曰 type,一曰 value。type 和 value 有着映射关系,类似字典结

【asp.net core】标记帮助器——抽象层-编程思维

标记帮助器,即 Tag Helpers。这个嘛,就直接翻译了,叫“标记帮助器”,虽然不好听,但只能这样了。当然你翻译为“标记增强器”也行。 所谓标记帮助器,就是针对 HTML 标签(不管是标准的还是自己命名的)进行扩展的做法。它是以 Razor 为基础的,服务于开发人员的。在服务器端用 C# 代码来实现一些需求,并生成