{"kind":"AgentDefinition","metadata":{"namespace":"community","name":"csharp-razorpages","version":"0.1.0"},"spec":{"agents_md":"---\ndescription: 'Razor Pages component and application patterns'\napplyTo: '**/*.cshtml, **/*.cshtml.cs'\n---\n\n## Razor Pages Code Style and Structure\n\n- Write idiomatic, efficient Razor Pages and C#.\n- Stick to the conventions the framework is built around: handler-based PageModels, not MVC controller patterns shoehorned into pages.\n- Keep PageModels focused on request/response orchestration; business logic belongs in injected domain services.\n- Trivial handlers can stay inline. For pages with lots of handlers and dependencies, reach for a mediator like MediatR.\n- Use async/await end-to-end so handlers don't block the request pipeline.\n\n## Naming Conventions\n\n- PascalCase for PageModel classes, handler methods, and public members (`CreateModel`, `OnPostAsync`, `OnPostDeleteAsync`).\n- camelCase for private fields and locals, with the `_` prefix on private fields per the .NET convention (`_context`, `_logger`).\n- Interface names start with \"I\" (`IEmailService`).\n- Named handlers drop the `OnPost`/`Async` affixes when routed. `OnPostJoinListAsync` is reached as `handler=JoinList`.\n\n## Model Binding and Overposting\n\n- Don't put `[BindProperty]` on EF or domain entities directly. An attacker can post extra fields like `IsAdmin` or `Secret` and the binder will happily set them, even if the form doesn't render them.\n- Bind to a dedicated Input Model or View Model that exposes only the properties the page is allowed to accept, then map to the entity.\n- `TryUpdateModelAsync\u003cT\u003e` with an explicit allow-list of properties is another option, especially in edit scenarios.\n- Avoid `[Bind]` for edits. Excluded properties get reset to `default(T)` rather than left alone, which is rarely what you want. Prefer Input Models.\n- Don't enable `[BindProperty(SupportsGet = true)]` broadly. Razor Pages skips GET binding by default for a reason; opt in per-property and validate what comes in.\n- For custom types (including strongly-typed IDs), implement `TryParse` or a `TypeConverter` so they bind from route and query values. Without one, the binder treats them as complex types and binding silently fails. One of those bugs that wastes an afternoon.\n- `[BindRequired]` and `[Required]` aren't the same thing. `[BindRequired]` errors when the source value is *absent* from the posted form; `[Required]` validates that the bound value isn't null/empty. `[BindRequired]` only applies to form binding, since JSON and XML go through input formatters instead.\n\n## Handler Methods and Request Flow\n\n- Always use Post-Redirect-Get on successful POSTs. Return `RedirectToPage(\"./Index\")`, never `Page()`. Returning `Page()` on success means a browser refresh resubmits the form.\n\n```csharp\npublic async Task\u003cIActionResult\u003e OnPostAsync()\n{\n    if (!ModelState.IsValid) return Page();          // re-render on error\n    await _service.CreateAsync(Input);\n    return RedirectToPage(\"./Index\");                // PRG on success\n}\n```\n\n- Guard every persistence path with `if (!ModelState.IsValid) return Page();`. Client-side validation can be bypassed; the server is authoritative.\n- Use a handler parameter (`OnGetAsync(int id)`) for single-request route or query values. Use `[BindProperty]` for POST data that needs to round-trip back to the view on validation errors.\n- Named handlers (`OnPostDeleteAsync`, `OnPostApproveAsync`) need the `asp-page-handler` tag helper on the submit button. Without it, plain buttons fall back to `OnPostAsync` or 404.\n- If `OnGet` does expensive work, add a lightweight `OnHead`. Razor Pages falls back to `OnGet` for HEAD requests otherwise, so every probe pays the full GET cost.\n- Filters work differently here than in MVC: `[ActionFilter]` attributes are silently ignored on page handlers. Use `IPageFilter` / `IAsyncPageFilter`, or register global conventions through `options.Conventions` in `Program.cs`.\n\n## Project Structure and Conventions\n\n- Shared layouts, partials, and templates go in `Pages/Shared/`, not `Views/Shared/`. Razor Pages resolves views hierarchically from the page's folder up through `Pages/`, and mixing in MVC conventions just fights the framework.\n- Set `Layout` in `Pages/_ViewStart.cshtml`. Use `Pages/_ViewImports.cshtml` for `@namespace`, `@addTagHelper`, and shared directives.\n- Keep `.cshtml` and `.cshtml.cs` colocated. Per-page locality is one of the main reasons to use Razor Pages in the first place, and splitting them across folders throws that away.\n\n## Security\n\n- Trust Razor's default `@` expression HTML encoding. Don't reach for `@Html.Raw()` on user-supplied content; it disables encoding and opens the door to XSS.\n- Stick with `\u003cform method=\"post\"\u003e` and the Form Tag Helper so the antiforgery token gets injected automatically. For AJAX or `fetch`, render the token with `@Html.AntiForgeryToken()` and send it as the `RequestVerificationToken` header.\n- Don't commit secrets to `appsettings.json`. Use `appsettings.{Environment}.json` for environment overrides, User Secrets (`dotnet user-secrets`) locally, and Azure Key Vault or environment variables in production. Bind via `IOptions\u003cT\u003e`.\n\n## Dependency Injection in PageModels\n\n- Watch for the scoped-in-singleton captive dependency trap. If a singleton holds a reference to a scoped service (like an EF `DbContext`), that instance leaks across requests. Common bug in PageModel-adjacent services.\n- Don't register a `DbContext` as `Singleton`. The default `AddDbContext` registration is `Scoped` for a reason.\n\n## Entity Framework Core in Page Handlers\n\n- Project EF entities to DTOs or View Models with `.Select(...)` before returning them to the view. Passing entities with navigation properties straight through causes lazy-loading exceptions, N+1 queries, or serialization cycles when the view renders.\n- Use `.AsNoTracking()` on read-only queries like list pages or details pages without edit. The change tracker has overhead you don't need there.\n- Prefer `FindAsync(key)` over `FirstOrDefaultAsync(x =\u003e x.Id == key)` when fetching by primary key without `Include`. `FindAsync` checks the change tracker first.\n\n## State Management\n\n- `TempData` is for one-shot, cross-redirect messages like flash notifications after a PRG. It's read-once, cookie-serialized by default, and not a substitute for session storage.\n- For actual per-user session state, use `ISession`. For per-request data, `HttpContext.Items`. For shared state within a single request, request-scoped DI services.\n- Call `TempData.Keep()` or `TempData.Peek()` when a value needs to survive multiple redirects without being consumed.\n\n## Testing\n\n- Unit-test `PageModel` classes directly. Instantiate them with mocked dependencies (Moq, NSubstitute) and assert on the returned `IActionResult`: `PageResult` for re-renders, `RedirectToPageResult` for successful PRG, `NotFoundResult` for 404 paths.\n- For integration tests that exercise routing, model binding, and antiforgery, use `WebApplicationFactory\u003cTEntryPoint\u003e` with `Microsoft.AspNetCore.Mvc.Testing`.\n- When testing handlers that read `ModelState`, populate it manually with `PageModel.ModelState.AddModelError(...)`. The binding pipeline doesn't run in unit tests.","description":"Razor Pages component and application patterns","import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/blob/541b7819d8c3545c6df122491af4fa1eae415779/instructions/csharp-razorpages.instructions.md"},"manifest":{}},"content_hash":[121,250,202,45,167,22,141,138,112,118,73,177,92,201,248,97,92,125,18,254,233,17,143,173,255,73,15,53,161,151,163,177],"trust_level":"unsigned","yanked":false}
