4.2 Razor 语法与页面辅助功能 Razor 语法的核心是 @ 符号的四种用法:输出表达式、语句块、指令、标记过渡;页面辅助功能则解决"页面写多了怎么复用"——分区组织目录、分部视图复用片段、视图组件封装自带数据的模块。语法十分钟,组织力才是本节的重点。 上一节搭好了页面骨架,本节解决两个日常问题:Razor 混写规则到底有几条(比想象中少);页面数量上来之后,导航、卡片、侧栏这些东西放在哪。观察哨的判断始终是管线式的:每件辅助工具都对应管线上的一个执行时机。 @ 符号的四副面孔 四副面孔覆盖全部日常:指令声明页面属性、语句块写逻辑、表达式输出值、关键字写流程。转义只需记两条:输出 @ 本身写 @@;输出 HTML 原文(非编码)用 Html.Raw 且只用于可信内容——3.
Razor 语法的核心是 @ 符号的四种用法:输出表达式、语句块、指令、标记过渡;页面辅助功能则解决"页面写多了怎么复用"——分区组织目录、分部视图复用片段、视图组件封装自带数据的模块。语法十分钟,组织力才是本节的重点。
上一节搭好了页面骨架,本节解决两个日常问题:Razor 混写规则到底有几条(比想象中少);页面数量上来之后,导航、卡片、侧栏这些东西放在哪。观察哨的判断始终是管线式的:每件辅助工具都对应管线上的一个执行时机。
@page <!-- 面孔一:指令,@ 开头 + 关键字,编译期处理 --> @model IndexModel <!-- 声明页面模型的类型 --> @{ var titles = new[] { "首页", "热销" }; <!-- 面孔二:语句块,执行不输出 --> var count = titles.Length; } <p>共 @count 个栏目</p> <!-- 面孔三:表达式,求值并输出(HTML 编码) --> <p>原始输出 @@符号</p> <!-- 连写两个 @ 输出一个 @ --> @if (count > 1) <!-- 面孔四:关键字过渡,C# 控制结构接管 --> { <text>多个栏目</text> <!-- text 标记:块内输出纯文本 --> }
四副面孔覆盖全部日常:指令声明页面属性、语句块写逻辑、表达式输出值、关键字写流程。转义只需记两条:输出 @ 本身写 @@;输出 HTML 原文(非编码)用 Html.Raw 且只用于可信内容——3.4 节说过,编码是防注入的第一道闸。
页面开发有两个高频小需求:判断当前环境(开发期才显示调试面板)、在 GET 与 POST 之间带一点状态。环境判断用环境标签助手,状态用页面模型属性与 TempData 的组合:
<environment include="Development"> <div class="debug-panel">仅开发环境可见的调试面板</div> </environment> <!-- 生产环境渲染后此块消失,不需要手写 if 判断环境变量 -->
public class EditModel : PageModel { [BindProperty] // 属性绑定:页面模型不必靠方法参数接值 public ProductInput Input { get; set; } = new(); [TempData] // 跨一次跳转存活:PRG 后仍能读到 public string? Message { get; set; } public IActionResult OnPost() { if (!ModelState.IsValid) return Page(); Message = $"已保存 {Input.Name}"; // 写入 TempData return RedirectToPage("Index"); // 跳转后 Index 页仍可读取显示 } }
BindProperty 让属性直接参与模型绑定(3.3 节的规则原样适用),页面通道里比方法参数更常见;TempData 属性把"跳转后还要显示一次"的场景写成一行。
页面过了三十个,三件事必然发生:目录需要分组、同样的片段在多个页面重复、某个"自带数据的小模块"到处粘贴。三件套各对一症:

三件套的调用方式各一行:
<!-- 分部视图:数据由调用方准备好 --> <partial name="_ProductCard" model="item" /> <!-- 视图组件:一行唤起,内部自己取数渲染 --> <vc:hot-list count="5" /> <!-- 分区里的链接:分区名进路由 --> <a asp-area="Admin" asp-page="/Reports/Index">后台报表</a>
TagHelper 家族可以扩展。页面里到处出现的"金额展示框"(带币种符号与样式),可以收进一个自定义标签:
// 自定义 TagHelper:把金额格式化逻辑收进 money 标签 public class MoneyTagHelper : TagHelper { public decimal Value { get; set; } // 标签属性自动映射 public string? Currency { get; set; } // 可选属性 public override void Process(TagHelperContext ctx, TagHelperOutput output) { var symbol = Currency == "USD" ? "$" : "¥"; output.TagName = "span"; // 输出标签改为 span output.Attributes.SetAttribute("class", "money"); output.Content.SetContent($"{symbol}{Value:N2}"); // N2:千分位两位小数 } }
<!-- 页面里:一处声明,处处整洁 --> <money value="@item.Price" currency="CNY" /> <!-- 渲染结果:<span class="money">¥199.00</span> -->
案例复盘:某电商站商品页与列表页各自手写金额格式化,一处改千分位规则漏改三处,金额显示不一致引发客服工单。收进 MoneyTagHelper 后格式化规则单一来源,一次修改全站生效。变式:需要异步取数的标签(徽标上显示库存)改写异步版本;配置驱动的属性(币种符号表)从 IConfiguration 注入——2.3 节的机制在自定义组件里照常可用。
两者都是进程内渲染,开销远小于一次数据库往返。真正的性能陷阱是"循环里调视图组件查库"——每行卡片查一次库。判定方法:看组件的 SQL 次数(第 5 章的日志观察窗能看到),而不是猜。
⚠️ 常见坑:把 TagHelper 写成了 HTML Helper 的风格竞赛。两者能力重叠,项目里应统一主线(TagHelper 为主、Html.Helper 兜底),混用两套风格会让后来者难辨"这个写法是特性还是遗留"。