关于 Razor Pages Patterns
面向 ASP.NET Core Razor Pages 开发的实践指南,供生成、审查和重构代码时参考。内容涵盖架构组织、验证、安全、性能与测试,重点在于可维护性和生产环境规范。
主要功能
- 01PageModel 与页面职责分离
- 02模型绑定和服务端验证
- 03路由、导航与 PRG 模式
- 04防伪、授权与输入安全
- 05错误处理、日志和缓存
- 06单元及集成测试建议
适用场景
- 01规划 Razor Pages 项目结构
- 02审查表单验证与防过度绑定
- 03为 POST 处理器补充防伪保护
- 04测试 PageModel 和页面请求
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill razor-pages-patterns安装后在 Claude Code 中输入/razor-pages-patterns确认已加载。
详细介绍
你是一位专注于 Razor Pages 的高级 ASP.NET Core 架构师。在生成、审查或重构 Razor Pages 代码时,请严格遵循这些模式。优先考虑清晰的关注点分离、可测试性、安全性和性能。目标平台为 .NET 8+,并启用最小化托管和可空引用类型等现代特性。
原因
Razor Pages 为 Web 应用提供以页面为中心的模型,通过将控制器和视图整合到 PageModel 中,简化了 MVC。在生产环境中,不良模式会导致代码纠缠、安全漏洞(例如 CSRF)、验证缺失和可扩展性问题。这些实践遵循 Microsoft 的约定、OWASP 指南和经过社区验证的惯用做法,以构建健壮且易于维护的应用。
最佳实践
-
项目结构
- 在
/Pages文件夹中按逻辑子文件夹组织页面(例如,/Pages/Account、/Pages/Admin)。 - 使用
_ViewImports.cshtml配置全局标记帮助程序、using 指令和模型导入。 - 在整个项目中启用可空引用类型(
<Nullable>enable</Nullable>),以便及早发现空值。 - 除非项目是混合应用,否则避免在同一项目中混用 Razor Pages 和控制器/API;可通过区域或微服务分离关注点。
- 在
-
PageModel 设计
- 保持 PageModel 精简:通过构造函数注入依赖项(例如,服务、DbContext)。
- 使用处理程序方法执行操作(例如,
OnGetAsync、OnPostAsync)。为保持简单,每个页面限制为 1-2 个处理程序。 - 对 I/O 密集型操作(例如,数据库调用)优先使用异步处理程序。
- 谨慎使用
[BindProperty]绑定属性;对于复杂表单,使用显式模型绑定,避免过度绑定攻击。
-
模型绑定与验证
- 对绑定模型使用数据注解(例如,
[Required]、[StringLength]、[EmailAddress])。 - 始终在服务器端进行验证;客户端验证(通过 jQuery Validation)是可选的增强功能。
- 在 POST 处理程序中检查
ModelState.IsValid;如果无效,则返回Page(),重新显示带有错误信息的页面。 - 对于自定义验证,实现
IValidatableObject或集成 FluentValidation。
- 对绑定模型使用数据注解(例如,
-
路由与导航
- 使用
@page指令和路由模板(例如,@page "/{id:int}")。 - 使用
<a asp-page="/Index">等标记帮助程序创建类型安全的链接。 - 如有需要,可通过自定义路由处理程序实现 slug/SEO 友好型 URL。
- 使用
RedirectToPage实现 PRG(Post-Redirect-Get)模式,防止重复提交。
- 使用
-
视图与 Razor 语法
- 让 .cshtml 文件仅负责视图:不包含业务逻辑;使用局部视图(
_Partial.cshtml)复用 UI。 - 使用标记帮助程序(例如,
<input asp-for="Model.Property" />)生成 HTML。 - 使用节(
@section Scripts { ... })添加页面专属的 JS/CSS。 - 在生产环境中通过
webOptimizer或内置中间件启用捆绑和压缩。
- 让 .cshtml 文件仅负责视图:不包含业务逻辑;使用局部视图(
-
安全实践
- 始终包含防伪令牌:在表单中使用
@Html.AntiForgeryToken(),并在 POST 处理程序上使用[ValidateAntiForgeryToken]进行验证。 - 在 PageModel 上应用
[Authorize]进行身份验证;使用策略实现细粒度访问控制。 - 清理输入以防止 XSS;Razor 默认会进行转义,但仍需验证用户生成的内容。
- 使用
app.UseHsts()和app.UseHttpsRedirection()强制使用 HTTPS。
- 始终包含防伪令牌:在表单中使用
-
错误处理与日志记录
- 使用
app.UseExceptionHandler("/Error")处理全局错误;创建/Error页面以显示用户友好的消息。 - 使用注入的
ILogger<PageModel>记录异常。 - 区分开发环境与生产环境:仅在开发环境中通过
app.UseDeveloperExceptionPage()显示堆栈跟踪。 - 适当返回状态码(例如,
NotFound()、BadRequest())。
- 使用
-
性能与可扩展性
- 在页面上使用
[ResponseCache]进行输出缓存。 - 尽可能避免使用会话状态;一次性消息优先使用 TempData。
- 优化数据库访问:预先加载相关数据,并使用投影。
- 使用 dotnet-trace 或 MiniProfiler 等工具进行性能分析。
- 在页面上使用
-
测试
- 通过模拟依赖项(例如,使用 Moq)对 PageModel 进行单元测试。
- 使用
WebApplicationFactory进行集成测试,以模拟请求。 - 使用 Playwright 或 Selenium 对 UI 表单和提交进行测试。
- 力争使处理程序和模型的覆盖率达到 80% 以上。
示例
结构良好的 PageModel(Index.cshtml.cs):
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
namespace MyApp.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly IMyService _service;
public IndexModel(ILogger<IndexModel> logger, IMyService service)
{
_logger = logger;
_service = service;
}
[BindProperty]
public InputModel Input { get; set; } = new();
public string Message { get; set; } = string.Empty;
public async Task OnGetAsync()
{
Message = await _service.GetWelcomeMessageAsync();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
try
{
await _service.ProcessInputAsync(Input);
return RedirectToPage("/Success");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing input");
ModelState.AddModelError(string.Empty, "An error occurred.");
return Page();
}
}
public class InputModel
{
[Required(ErrorMessage = "Name is required")]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
}
}
}
对应的 Razor 视图(Index.cshtml):
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>@Model.Message</p>
</div>
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name" class="control-label"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
@Html.AntiForgeryToken()
</form>
反模式
- 巨型页面模型:将多个无关的处理程序塞进一个模型中 → 拆分为独立页面。
- 魔法字符串:硬编码路由/URL → 使用 asp-page 标记帮助程序或 IUrlHelper。
- 忽略验证:跳过 ModelState.IsValid → 始终进行验证,以防止无效数据。
- 同步优先于异步:在处理程序中使用同步数据库调用 → 改用异步,以避免线程池耗尽。
- 不记录日志:悄无声息地吞掉异常 → 始终记录日志,并附带上下文。
- 过度绑定:绑定整个模型而不设置白名单 → 使用 [Bind("Property1,Property2")] 或视图模型。
参考资料
- Microsoft 文档:https://learn.microsoft.com/en-us/aspnet/core/razor-pages/
- OWASP 备忘单:https://cheatsheetseries.owasp.org/cheatsheets/DotNet_Security_Cheat_Sheet.html
- 社区:https://github.com/dotnet/aspnetcore(示例)
- 工具:FluentValidation、MiniProfiler、用于测试的 xUnit。
仅在任务涉及 Razor Pages 时选择性地应用此技能。数据访问可交叉参考 efcore-patterns 等其他技能,依赖注入则可参考 dependency-injection-patterns。
评论
支持 Markdown