# Razor Pages

> ASP.NET Core Razor Pages patterns for server-rendered web apps.

- Skill: `ngxtm-devkit/razor-pages` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add ngxtm-devkit/razor-pages`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ngxtm-devkit/razor-pages/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ngxtm (https://skillmd.com/u/ngxtm-devkit)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ngxtm-devkit/razor-pages

---


# Razor Pages

## **Priority: P1 (OPERATIONAL)**

ASP.NET Core Razor Pages patterns for server-rendered web applications.

## Implementation Guidelines

- **PageModel**: Keep logic in PageModel, not in `.cshtml`. Use handlers (`OnGet`, `OnPost`).
- **Model Binding**: Use `[BindProperty]` for form data. `[FromQuery]` for query params.
- **Tag Helpers**: Use `asp-for`, `asp-action`, `asp-route-*` for type-safe HTML.
- **Partial Views**: Reusable UI components with `<partial>` tag helper.
- **View Components**: Complex reusable UI with server logic.
- **Layout**: Shared layout in `_Layout.cshtml`. Use sections for page-specific content.
- **TempData**: Flash messages with PRG (Post-Redirect-Get) pattern.

## Anti-Patterns

- **No logic in `.cshtml`**: Move to PageModel or View Components.
- **No `ViewBag`/`ViewData`**: Use strongly-typed models.
- **No inline CSS/JS**: Use external files with bundling.
- **No form without anti-forgery**: Always include `@Html.AntiForgeryToken()` or use `asp-antiforgery`.

## Code

```csharp
// Pages/Users/Create.cshtml.cs
public class CreateModel(IUserService userService, ILogger<CreateModel> logger) : PageModel
{
    [BindProperty]
    public CreateUserInput Input { get; set; } = new();

    public List<SelectListItem> Roles { get; set; } = [];

    public async Task OnGetAsync()
    {
        Roles = await GetRolesAsync();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            Roles = await GetRolesAsync();
            return Page();
        }

        var result = await userService.CreateAsync(Input);

        if (!result.IsSuccess)
        {
            ModelState.AddModelError(string.Empty, result.Error!);
            Roles = await GetRolesAsync();
            return Page();
        }

        TempData["Success"] = "User created successfully";
        return RedirectToPage("./Index");
    }
}
```

```html
@* Pages/Users/Create.cshtml *@
@page
@model CreateModel

<h2>Create User</h2>

@if (TempData["Error"] is string error)
{
    <div class="alert alert-danger">@error</div>
}

<form method="post">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <div class="mb-3">
        <label asp-for="Input.Name" class="form-label"></label>
        <input asp-for="Input.Name" class="form-control" />
        <span asp-validation-for="Input.Name" class="text-danger"></span>
    </div>

    <div class="mb-3">
        <label asp-for="Input.Email" class="form-label"></label>
        <input asp-for="Input.Email" class="form-control" type="email" />
        <span asp-validation-for="Input.Email" class="text-danger"></span>
    </div>

    <div class="mb-3">
        <label asp-for="Input.RoleId" class="form-label"></label>
        <select asp-for="Input.RoleId" asp-items="Model.Roles" class="form-select">
            <option value="">Select a role</option>
        </select>
        <span asp-validation-for="Input.RoleId" class="text-danger"></span>
    </div>

    <button type="submit" class="btn btn-primary">Create</button>
    <a asp-page="./Index" class="btn btn-secondary">Cancel</a>
</form>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
```

## Reference & Examples

For page conventions, view components, and AJAX patterns:
See [references/REFERENCE.md](references/REFERENCE.md).

## Related Topics

aspnet-core | blazor | security

