news 2026/10/15 13:38:48

practical-aspnetcore 端点路由实战:按请求方法从 Razor Page 端点元数据中读取特性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
practical-aspnetcore 端点路由实战:按请求方法从 Razor Page 端点元数据中读取特性
  • 示例工程

【免费下载链接】practical-aspnetcore

Practical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.

项目地址:https://gitcode.com/gh_mirrors/pr/practical-aspnetcore
点击查看免费下载

导读

本篇文章基于 practical-aspnetcore 仓库中的new-routing-12示例,讲解在 ASP.NET Core 端点路由体系下,如何从 Razor Page 生成的Endpoint中按请求方法(GET / POST)读取自定义特性(Attribute)元数据。由于 Razor Page 无法像 MVC 那样把特性挂在 Handler 方法上,示例给出了一个"特性挂在 PageModel 类、中间件按 HTTP 动词挑选字段"的额外步骤方案。读完本文,你将掌握HttpContext.GetEndpoint()、Endpoint.Metadata.GetMetadata<T>()以及HttpContext.Items组合使用的方法,能够在自定义中间件中实现与请求动词相关的端点元数据传递。

背景:端点路由中的元数据(Metadata)

ASP.NET Core 3.0 引入端点路由后,每个可执行的请求目标(MVC 控制器动作、Razor Page、MapGet内联委托等)都会被抽象为一个Endpoint对象。Endpoint不仅包含路由匹配信息(如DisplayName),还携带一组元数据(Metadata)——也就是各类Attribute实例的集合。中间件可以通过HttpContext.GetEndpoint()拿到当前请求匹配到的端点,再通过Endpoint.Metadata.GetMetadata<T>()取出挂载在端点上的自定义特性。

本仓库中new-routing-10展示了最基础的一步——在中间件里获取当前Endpoint并检查DisplayName,new-routing-11则展示了用GetMetadata<T>()直接读取特性值。本示例(new-routing-12)在其基础上再进一步:让同一份特性在不同请求方法下返回不同的值。

问题:Razor Page 的 Handler 方法挂不上特性

Razor Page 的请求处理逻辑写在一组约定命名的 Handler 方法里,例如OnGet()、OnPost()。与 MVC 不同,你不能在 Razor Page 的 Handler 方法上直接贴自定义特性,特性只能标注在对应的PageModel类上:

// ❌ Razor Page 中不能这样做 public class AboutModel : PageModel { [Message(ContentGet = "...", ContentPost = "...")] // 不合法 public void OnGet() { } [Message(...)] // 不合法 public void OnPost() { } }

对比之下,MVC 控制器可以把特性直接标注在 Action 方法上。仓库中的new-routing-13示例就展示了这一点——HomeController的About()与AboutPost()方法分别贴上了内容不同的[Message(...)]特性(见 Controllers/HomeController.cs),端点路由会为每个 Action 单独生成一个Endpoint,各自的元数据天然区分。

正因 Razor Page 无法走这条捷径,new-routing-12的文档明确指出:"Unlike in MVC, you can't use Attribute from the method of a Razor Page. You can only use it from the Model class. This makes obtaining the appropriate metadata for each request require an extra step."(与 MVC 不同,你不能在 Razor Page 的方法上使用特性,只能在 Model 类上使用,这使得为每个请求获取合适的元数据需要额外的步骤。)

解决方案:特性挂类、中间件按动词挑选

示例采用的思路分三步:

  1. 自定义特性内置 GET / POST 两个字段,而不是单一值;
  2. 把特性整体标注在 PageModel 类上(这是 Razor Page 唯一允许的位置);
  3. 中间件在拿到端点元数据后,根据当前请求的 HTTP 动词,从特性对象中挑选对应的字段放入HttpContext.Items,页面 Handler 再取出来展示。

这样虽然多了一个"按方法分发"的步骤,但特性值依旧能精准区分 GET 与 POST 两种请求。

源码解读

1. 定义双值特性MessageAttribute

特性定义在 Program.cs 中,与中间件同文件放置:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class MessageAttribute : System.Attribute { public string ContentGet { get; set; } public string ContentPost { get; set; } }

要点:

  • AttributeUsage同时允许标注在类(Class)和方法(Method)上。尽管本示例只用在类上,保留Method目标可以保证该特性在 MVC 场景(如new-routing-13的控制器动作)下同样可用;
  • 特性用属性(Property)而非构造函数参数承载数据,使用命名参数语法[Message(ContentGet = "...", ContentPost = "...")]赋值。

2. 在 PageModel 上标注特性

Pages/about.cshtml.cs 是整个示例的核心:

[Message(ContentGet = "This message only shows up on GET", ContentPost = "This message only shows up on POST")] public class AboutModel : PageModel { public string Message { get; set; } public void OnGet() { Message = HttpContext.Items["GreetingFromMiddleWare"] as string; } public void OnPost() { Message = HttpContext.Items["GreetingFromMiddleWare"] as string; } }

可以看到:

  • 特性标注在AboutModel类上,这是 Razor Page 唯一合法的挂载点;
  • GET 与 POST 两个动词对应的文案分别存于ContentGet、ContentPost;
  • OnGet与OnPost两个 Handler 都从HttpContext.Items["GreetingFromMiddleWare"]读取中间件写入的消息。因为中间件已按动词挑选过文案,这里不需要再区分方法,逻辑保持统一。

3. 中间件按请求方法挑选元数据

GreeterMiddleware(同样定义在 Program.cs)执行关键分发逻辑:

public async Task InvokeAsync(HttpContext httpContext) { Endpoint endPoint = httpContext.GetEndpoint(); var message = endPoint.Metadata.GetMetadata<MessageAttribute>(); if (message != null) { if (httpContext.Request.Method == HttpMethods.Get) httpContext.Items.Add("GreetingFromMiddleWare", message.ContentGet); else if (httpContext.Request.Method == HttpMethods.Post) httpContext.Items.Add("GreetingFromMiddleWare", message.ContentPost); } await _next.Invoke(httpContext); }

流程拆解:

  1. httpContext.GetEndpoint():取得当前请求匹配到的端点。由于MapRazorPages()会为每个页面(页面路由 + 各 Handler 组合)生成端点,/about页面的 GET 与 POST 对应的是同一个端点(它们共享同一个PageModel类型),因此拿到的是同一份元数据;
  2. endPoint.Metadata.GetMetadata<MessageAttribute>():从端点元数据集合中取出MessageAttribute实例;取不到时返回null(所以调用前要做空值判断);
  3. 按动词分发:通过httpContext.Request.Method与HttpMethods.Get/HttpMethods.Post常量比较,决定取ContentGet还是ContentPost;
  4. 写入HttpContext.Items:中间件与页面 Handler 之间通过请求级字典Items传递数据,页面的OnGet/OnPost再原样取出。

中间件构造函数中还注入了LinkGenerator字段(readonly LinkGenerator _linkGenerator),本示例并未实际使用它——这是为了与仓库中其他中间件示例保持结构一致,读者可以忽略或移除。

4. 页面视图与入口页

Pages/about.cshtml 只做一件事——渲染@Model.Message:

@page @model PracticalAspNetCore.AboutModel <html> <body> <h2>@Model.Message</h2> </body> </html>

Pages/index.cshtml 提供了两种触发方式,便于验证:

@page <html> <body> <h1>An example of accessing Razor Page attribute values from a middleware using Endpoint class</h1> <ul> <li><a href="/about">/about</a></li> <li><form asp-page="./about"><input type="submit" value="POST /About"/></form></li> </ul> </body> </html>
  • 第一个链接是常规的GET /about;
  • 第二个是asp-page="./about"表单(该 Tag Helper 通过 Pages/_ViewImports.cshtml 中的@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers启用),点击按钮即向同一页面发起POST /about请求。

因此同一个 URL、同一次中间件逻辑,GET 显示"This message only shows up on GET",POST 显示"This message only shows up on POST",验证了"按请求方法取元数据"的效果。

5. 启动装配

Program.cs 的装配部分非常简洁:

var builder = WebApplication.CreateBuilder(); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); var app = builder.Build(); app.UseMiddleware<GreeterMiddleware>(); app.MapRazorPages(); app.Run();
  • 注册 Razor Pages 与 MVC(含视图)服务;
  • 把GreeterMiddleware挂到管道中(位于MapRazorPages()之前,确保请求先经过中间件);
  • MapRazorPages()为所有页面生成端点。

项目文件 new-routing-12.csproj 的目标框架为net10.0,仓库根目录的 global.json 要求 .NET SDK10.0.0(rollForward: major,允许预发布版)。

运行与验证

在仓库根目录执行:

dotnet restore projects/endpoint-routing/new-routing-12/new-routing-12.csproj dotnet run --project projects/endpoint-routing/new-routing-12/new-routing-12.csproj

启动后:

  1. 浏览器访问/,点击/about链接(GET 请求),页面显示This message only shows up on GET;
  2. 返回首页,点击POST /About按钮(表单 POST 请求),页面显示This message only shows up on POST。

两次请求命中同一个/about端点,输出却随动词变化——这正是本文所述"额外步骤"带来的按请求方法分发的元数据能力。

与仓库其他端点元数据示例的对照

示例主题与本文的关系
new-routing-10中间件中用GetEndpoint()检查DisplayName前置基础:拿到端点对象
new-routing-11用GetMetadata<T>()读取单一特性值本文的基础版:单一Content字段,不区分动词
new-routing-13MVC 控制器在 Action 方法上贴特性对照版:MVC 天然按方法区分,无需额外步骤
new-routing-14通过IEndpointFeature获取EndpointGetEndpoint()的等价实现路径
new-routing-15用WithMetadata给内联中间件附加元数据另一种"生产"元数据的途径

其中new-routing-11与本文差异最值得对比:前者只定义一个Content属性,中间件取到后直接使用;后者将属性拆为ContentGet/ContentPost,并在中间件里根据Request.Method分发。这正是 Razor Page 单端点、多 Handler 结构下,弥补"特性不能挂 Handler 方法"限制的实用模式。

小结

new-routing-12完整演示了在 Razor Page 场景下按请求方法读取端点元数据的可行路径:

  • 约束认知:Razor Page 的特性只能标注在PageModel类上,无法像 MVC 那样细化到 Handler 方法;
  • 解决手法:特性内用多个属性对应多个动词,中间件里用GetEndpoint()+Metadata.GetMetadata<T>()取出特性,再以Request.Method决定取哪个字段;
  • 数据传递:通过HttpContext.Items把结果从中间件安全地送到页面 Handler,保持页面逻辑与路由/元数据解耦。

这一模式同样适用于鉴权信息、A/B 文案、功能开关等按动词区分、又希望集中在管道层统一处理的元数据场景,是理解 ASP.NET Core 端点路由与元数据机制的优质实战范例。

  • 示例工程

【免费下载链接】practical-aspnetcore

Practical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.

项目地址:https://gitcode.com/gh_mirrors/pr/practical-aspnetcore
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 13:38:22

【AI黑话日日新】Day 061|Continuous Batching(连续批处理)

让贵重的显卡一刻不闲:谁算完谁让位,新请求立刻补上。 1. 它到底在说什么 连续批处理(Continuous Batching)是一种大模型在线服务的调度方式。你可以把它想象成一辆不停靠站的微循环小巴:乘客(请求)一上车就出发,谁到了目的地(生成结束)立刻下车,空出的座位下一站马…

作者头像 李华
网站建设 2026/10/15 13:38:04

Spring AI客服项目收尾复盘:事件总线、Redis Pub/Sub与断线补拉实战

项目收尾那天&#xff0c;客服主管跑过来问我&#xff1a;“你们说的 AI 客服助手&#xff0c;用户聊到一半断网再回来&#xff0c;之前的内容能续上吗&#xff1f;”我当时一愣&#xff0c;因为演示环境一直走的是内网&#xff0c;断线续聊这块还真没单独压过。这个问题直接把…

作者头像 李华
网站建设 2026/10/15 13:37:35

Vue 3中shallowRef的性能优化与应用场景

1. 理解shallowRef的核心定位在Vue 3的响应式系统中&#xff0c;shallowRef是一个常被忽视但极具性能优化潜力的API。与常规的ref不同&#xff0c;shallowRef只会对.value属性本身进行响应式处理&#xff0c;而不会递归地对嵌套对象进行深度响应式转换。这种特性使得它在特定场…

作者头像 李华
网站建设 2026/10/15 13:37:28

Kubernetes CronJob核心原理与高可用实践指南

1. 项目概述&#xff1a;为什么定时任务在Kubernetes里不能只靠“写个脚本crontab”解决&#xff1f;“pod调度之CrontJob&#xff1a;定时任务”——这标题看着平实&#xff0c;但背后藏着K8s生产环境里一个高频踩坑区。我带过的三个不同行业的集群&#xff08;某电商大促中台…

作者头像 李华
网站建设 2026/10/15 13:37:04

Spring Boot 3整合Spring Security 6:登录校验与权限认证实战

做后端这些年&#xff0c;我见过太多把登录校验和权限控制写成一坨 if/else、拦截器加 ThreadLocal 到处传参的项目。刚开始接触 Spring Security 的时候&#xff0c;我也会觉得这框架“反人类”&#xff0c;配置多、概念抽象、文档读着读着就绕晕了。后来真正在一个某后台管理…

作者头像 李华