mcpField
R
Razor Pages Patterns

Razor Pages Patterns

github.com/wshaddix开发86115 次安装35 周前更新
代码仓库

关于 Razor Pages Patterns

面向 ASP.NET Core Razor Pages 开发的实践指南,供生成、审查和重构代码时参考。内容涵盖架构组织、验证、安全、性能与测试,重点在于可维护性和生产环境规范。

主要功能

  1. 01PageModel 与页面职责分离
  2. 02模型绑定和服务端验证
  3. 03路由、导航与 PRG 模式
  4. 04防伪、授权与输入安全
  5. 05错误处理、日志和缓存
  6. 06单元及集成测试建议

适用场景

  1. 01规划 Razor Pages 项目结构
  2. 02审查表单验证与防过度绑定
  3. 03为 POST 处理器补充防伪保护
  4. 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 指南和经过社区验证的惯用做法,以构建健壮且易于维护的应用。

最佳实践
  1. 项目结构

    • 在 /Pages 文件夹中按逻辑子文件夹组织页面(例如,/Pages/Account、/Pages/Admin)。
    • 使用 _ViewImports.cshtml 配置全局标记帮助程序、using 指令和模型导入。
    • 在整个项目中启用可空引用类型(<Nullable>enable</Nullable>),以便及早发现空值。
    • 除非项目是混合应用,否则避免在同一项目中混用 Razor Pages 和控制器/API;可通过区域或微服务分离关注点。
  2. PageModel 设计

    • 保持 PageModel 精简:通过构造函数注入依赖项(例如,服务、DbContext)。
    • 使用处理程序方法执行操作(例如,OnGetAsync、OnPostAsync)。为保持简单,每个页面限制为 1-2 个处理程序。
    • 对 I/O 密集型操作(例如,数据库调用)优先使用异步处理程序。
    • 谨慎使用 [BindProperty] 绑定属性;对于复杂表单,使用显式模型绑定,避免过度绑定攻击。
  3. 模型绑定与验证

    • 对绑定模型使用数据注解(例如,[Required]、[StringLength]、[EmailAddress])。
    • 始终在服务器端进行验证;客户端验证(通过 jQuery Validation)是可选的增强功能。
    • 在 POST 处理程序中检查 ModelState.IsValid;如果无效,则返回 Page(),重新显示带有错误信息的页面。
    • 对于自定义验证,实现 IValidatableObject 或集成 FluentValidation。
  4. 路由与导航

    • 使用 @page 指令和路由模板(例如,@page "/{id:int}")。
    • 使用 <a asp-page="/Index"> 等标记帮助程序创建类型安全的链接。
    • 如有需要,可通过自定义路由处理程序实现 slug/SEO 友好型 URL。
    • 使用 RedirectToPage 实现 PRG(Post-Redirect-Get)模式,防止重复提交。
  5. 视图与 Razor 语法

    • 让 .cshtml 文件仅负责视图:不包含业务逻辑;使用局部视图(_Partial.cshtml)复用 UI。
    • 使用标记帮助程序(例如,<input asp-for="Model.Property" />)生成 HTML。
    • 使用节(@section Scripts { ... })添加页面专属的 JS/CSS。
    • 在生产环境中通过 webOptimizer 或内置中间件启用捆绑和压缩。
  6. 安全实践

    • 始终包含防伪令牌:在表单中使用 @Html.AntiForgeryToken(),并在 POST 处理程序上使用 [ValidateAntiForgeryToken] 进行验证。
    • 在 PageModel 上应用 [Authorize] 进行身份验证;使用策略实现细粒度访问控制。
    • 清理输入以防止 XSS;Razor 默认会进行转义,但仍需验证用户生成的内容。
    • 使用 app.UseHsts() 和 app.UseHttpsRedirection() 强制使用 HTTPS。
  7. 错误处理与日志记录

    • 使用 app.UseExceptionHandler("/Error") 处理全局错误;创建 /Error 页面以显示用户友好的消息。
    • 使用注入的 ILogger<PageModel> 记录异常。
    • 区分开发环境与生产环境:仅在开发环境中通过 app.UseDeveloperExceptionPage() 显示堆栈跟踪。
    • 适当返回状态码(例如,NotFound()、BadRequest())。
  8. 性能与可扩展性

    • 在页面上使用 [ResponseCache] 进行输出缓存。
    • 尽可能避免使用会话状态;一次性消息优先使用 TempData。
    • 优化数据库访问:预先加载相关数据,并使用投影。
    • 使用 dotnet-trace 或 MiniProfiler 等工具进行性能分析。
  9. 测试

    • 通过模拟依赖项(例如,使用 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")] 或视图模型。

参考资料

仅在任务涉及 Razor Pages 时选择性地应用此技能。数据访问可交叉参考 efcore-patterns 等其他技能,依赖注入则可参考 dependency-injection-patterns。

评论

支持 Markdown