Razor Pages

This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14.

This section’s bibliography lists the reference material consulted while preparing these pages.

A Razor Page pairs one .cshtml file with one PageModel code-behind class, so each URL has a single, self-contained handler instead of sharing a controller with unrelated actions.

Registering Razor Pages

builder.Services.AddRazorPages();

var app = builder.Build();
app.MapRazorPages();

Pages live under Pages/ by convention; Pages/Products/Edit.cshtml is served at /Products/Edit. See Introduction to Razor Pages.

@page and route templates

@page must be the first directive in the file — it both marks the file as a page and can carry a route template, exactly like [Route] on a controller:

@page "{id:int?}"
@model EditModel
<form method="post">
    <input asp-for="Product.Name" />
    <span asp-validation-for="Product.Name"></span>
    <button>Save</button>
</form>

Constraints (\{id:int}), optional segments (\{id?}), and catch-alls work the same as in routing. See Razor Pages route and app conventions.

PageModel and handler selection

public sealed class EditModel(IProductService products) : PageModel
{
    [BindProperty] public ProductInput Product { get; set; } = new();

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id is int i) Product = await products.GetInputAsync(i);
        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid) return Page();
        await products.SaveAsync(Product);
        return RedirectToPage("Index");
    }
}

Handlers are selected by HTTP verb from the method name: OnGet/OnGetAsync, OnPost/OnPostAsync, and so on. A page can define named handlers for more than one action per page:

public async Task<IActionResult> OnPostDeleteAsync(int id) { ... }   // handler = "Delete"
<form method="post" asp-page-handler="Delete" asp-route-id="@p.Id">
    <button>Delete</button>
</form>

[BindProperty]

[BindProperty] model-binds a property from the request the same way an action parameter would; add SupportsGet = true to also bind it on GET (off by default, since binding on GET from arbitrary query values is rarely intended):

[BindProperty(SupportsGet = true)]
public string? Search { get; set; }

See Model Binding and Validation for binding sources and validation, which apply identically to Razor Pages and MVC.

Page conventions

Configure routing, authorization, and folder-wide behavior from AddRazorPages, instead of repeating attributes on every page:

builder.Services.AddRazorPages(o =>
{
    o.Conventions.AuthorizeFolder("/Admin");
    o.Conventions.AllowAnonymousToPage("/Admin/Login");
    o.Conventions.AddPageRoute("/Products/Details", "products/{id:int}/details");
});

Filters

IPageFilter / IAsyncPageFilter run around page handler execution — the Razor Pages analog of MVC action filters:

public sealed class AuditPageFilter : IAsyncPageFilter
{
    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) => Task.CompletedTask;

    public async Task OnPageHandlerExecutionAsync(
        PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
    {
        // runs immediately before the handler
        await next();
        // runs after the handler, before the result executes
    }
}

Register it globally (o.Filters.Add<AuditPageFilter>() in AddRazorPages) or per page via a [TypeFilter] on the PageModel. See Filters and the MVC Pipeline for the full filter pipeline shared with MVC.

Razor Pages vs. MVC

Choose When

Razor Pages

The site is a set of pages/forms; each URL has one clear responsibility; you want the view and its handler colocated.

MVC

Many actions share a controller, filters, or a non-page-shaped URL space; building HTTP APIs alongside views (see MVC Controllers and Views).

Both share the same routing, model binding, validation, and Razor rendering pipeline underneath, so the choice is organizational, not a difference in capability. See Razor Pages vs MVC controllers and views.