What Are Razor Pages?
Razor Pages, introduced in ASP.NET Core 2.0, is a page-focused programming model that pairs a .cshtml view file directly with a code-behind class called a PageModel (in a matching .cshtml.cs file), both living together in the Pages folder by convention. This removes the need to route every page through a separate controller class, which makes simple, page-centric scenarios like login forms, contact forms, or CRUD screens faster to build and easier to navigate because the markup and its logic sit side by side.
Cricket analogy: It's like how a batter's stance and their shot selection are tightly coupled to the specific ball they face in that over, rather than a separate coach calling every shot from the pavilion the way Virat Kohli reads the bowler's line and reacts on the spot instead of waiting for instructions from the dressing room.
Handler Methods and HTTP Verbs
Each PageModel exposes handler methods named by convention after the HTTP verb they respond to: OnGet or OnGetAsync runs when the page is requested with GET, and OnPost or OnPostAsync runs on form submission via POST. When a single page needs more than one POST action, such as both 'Save' and 'Delete' buttons, named handlers let you disambiguate them using the asp-page-handler tag helper attribute, which the framework maps to methods like OnPostDelete or OnPostDeleteAsync.
Cricket analogy: This mirrors how an umpire's signal differs by situation: a raised finger means out, arms crossed means no-ball, and a specific wide-ball signal exists too, so the same umpire dispatches to a different 'handler' depending on what happened on the field, just as OnGet and OnPost dispatch by verb.
// Pages/Products/Edit.cshtml.cs
public class EditModel : PageModel
{
private readonly ProductContext _context;
public EditModel(ProductContext context) => _context = context;
[BindProperty]
public Product Product { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int id)
{
Product = await _context.Products.FindAsync(id);
if (Product is null) return NotFound();
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid) return Page();
_context.Attach(Product).State = EntityState.Modified;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var product = await _context.Products.FindAsync(id);
if (product is not null)
{
_context.Products.Remove(product);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}Routing and Model Binding
Razor Pages routing is convention-based on folder structure: a file at Pages/Products/Details.cshtml is served at the URL /Products/Details without any explicit route configuration, and the @page directive at the top of the .cshtml file can add route parameters, for example @page "{id:int}" to accept a numeric id segment. Properties on the PageModel marked with [BindProperty] are automatically populated from posted form data by name-matching convention, and pairing them with DataAnnotations attributes like [Required] or [StringLength] lets ModelState.IsValid drive server-side validation before you touch the database.
Cricket analogy: This is like how a stadium's gate number directly corresponds to the seating block printed on your ticket — Gate 7 leads to Block C without needing a separate signpost system, just as folder structure maps directly to URL without extra route configuration.
Razor Pages forms rendered with the form tag helper automatically include a hidden antiforgery token input, and the framework validates it on POST without any extra code from you, protecting handlers against cross-site request forgery by default.
When to Choose Razor Pages
Razor Pages shines for page-centric workflows such as forms, wizards, and CRUD screens where each URL corresponds to one clear user task, because the tight PageModel-to-view pairing minimizes ceremony. For applications with heavy API surfaces, complex cross-cutting concerns shared across many unrelated pages, or teams that want a single controller fronting multiple views and content-negotiated responses, the traditional MVC controller/action model (or a hybrid of both, which ASP.NET Core fully supports in the same project) is often a better structural fit.
Cricket analogy: This is like choosing a specialist opener suited to swinging conditions at Headingley versus an all-rounder like Ravindra Jadeja who handles bowling, batting, and fielding duties across varied situations — pick the tool that matches the specific job.
Do not scatter business logic directly inside .cshtml markup just because Razor Pages colocates view and code — keep the PageModel thin by delegating real logic to injected services, otherwise pages become hard to unit test and the separation of concerns you gained from the code-behind file erodes.
- Razor Pages pairs a .cshtml view with a PageModel code-behind class, both under the Pages folder.
- Handler methods like OnGet and OnPost map to HTTP verbs by naming convention.
- Named handlers (OnPostDelete, invoked via asp-page-handler) let one page support multiple POST actions.
- Routing is convention-based on folder structure; @page can declare route parameters like "{id:int}".
- [BindProperty] auto-populates PageModel properties from form posts by name matching.
- DataAnnotations plus ModelState.IsValid provide server-side validation before persisting data.
- Razor Pages and MVC controllers can coexist in the same ASP.NET Core project; choose per scenario.
Practice what you learned
1. In a Razor Pages app, which file naming convention pairs a view with its code-behind logic?
2. Which attribute lets you route a form's POST to a specifically named handler like OnPostDelete?
3. What does [BindProperty] do on a PageModel property?
4. How is the default route for Pages/Products/Details.cshtml determined?
5. Which scenario is Razor Pages generally the better structural fit for compared to MVC controllers?
Was this page helpful?
You May Also Like
The MVC Pattern in ASP.NET Core
Understand how ASP.NET Core implements Model-View-Controller, separating request handling, business/data concerns, and presentation into distinct, testable layers.
Tag Helpers and Partial Views
Learn how tag helpers let server-side logic participate in HTML-like markup, and how partial views let you extract and reuse chunks of Razor UI across pages.
Blazor vs MVC vs Razor Pages
Compare ASP.NET Core's three UI programming models — Blazor, MVC, and Razor Pages — to understand their rendering models, state handling, and when each is the right choice.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics