Skip to main content

Overview

ASP.NET Core Razor Pages uses the PageModel pattern to separate presentation logic from the view. Each .cshtml Razor Page has a corresponding .cshtml.cs code-behind file containing a PageModel class that handles HTTP requests and prepares data for the view.

PageModel Architecture

PageModel classes inherit from PageModel base class and serve as the controller for a Razor Page. They encapsulate:
  • Request handling (OnGet, OnPost, etc.)
  • Model binding
  • Data access through injected services
  • Validation logic
  • Navigation and redirects

Class Structure

A typical PageModel in SupermarketWEB follows this structure:
[Authorize] // Optional: Require authentication
public class IndexModel : PageModel
{
    private readonly SupermarketContext _context;

    // Constructor with dependency injection
    public IndexModel(SupermarketContext context)
    {
        _context = context;
    }

    // Properties for data binding
    public IList<Product> Products { get; set; } = default!;

    // Handler methods
    public async Task OnGetAsync()
    {
        // GET request logic
    }
}

Handler Methods

Handler methods are called automatically based on HTTP verb and naming convention.

OnGet / OnGetAsync

Handles GET requests, typically used to retrieve and display data:
public async Task OnGetAsync() 
{
    if (_context.Categories != null) 
    {
        Categories = await _context.Categories.ToListAsync();
    }
}

OnPost / OnPostAsync

Handles POST requests, typically for form submissions:
[BindProperty]
public Category Category { get; set; } = default!;

public async Task<IActionResult> OnPostAsync() 
{
    if (!ModelState.IsValid || _context.Categories == null || Category == null) 
    {
        return Page();
    }

    _context.Categories.Add(Category);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Model Binding

The [BindProperty] attribute enables automatic binding of form data to properties:
[BindProperty]
public Category Category { get; set; } = default!;
By default, [BindProperty] only binds on POST requests. To bind on GET requests, use [BindProperty(SupportsGet = true)].
When a form is submitted:
  1. ASP.NET Core automatically maps form fields to the bound property
  2. Model validation is performed based on data annotations
  3. The bound data is available in OnPost handler methods

Common Patterns

CRUD Operations Pattern

SupermarketWEB follows a consistent pattern across all entities:

Dependency Injection Pattern

All PageModels receive services through constructor injection:
private readonly SupermarketContext _context;

public IndexModel(SupermarketContext context) 
{
    _context = context;
}
Location Examples:
  • Pages/Categories/Index.cshtml.cs:15
  • Pages/Products/Index.cshtml.cs:15
  • Pages/Account/Login.cshtml.cs:15

Authorization Pattern

Protected pages use the [Authorize] attribute:
[Authorize]
public class IndexModel : PageModel
{
    // Only authenticated users can access this page
}
Most CRUD pages (Categories, Products, Customers, PayModes) require authentication, while the Login page does not.

Return Types

PageModel handler methods commonly return:
void / Task
void
Used when the page should be rendered automatically
IActionResult
IActionResult
Provides flexibility to return different results (Page, Redirect, NotFound, etc.)
Page()
PageResult
Renders the associated Razor Page with current model state
RedirectToPage()
RedirectToPageResult
Redirects to another Razor Page, typically after successful POST operations
NotFound()
NotFoundResult
Returns HTTP 404 when requested resource doesn’t exist

ModelState Validation

PageModels automatically validate bound properties based on data annotations:
public async Task<IActionResult> OnPostAsync() 
{
    if (!ModelState.IsValid) 
    {
        return Page(); // Re-render page with validation errors
    }

    // Proceed with data operations
}

Page Organization

SupermarketWEB organizes pages by feature:
Pages/
├── Categories/
│   ├── Index.cshtml.cs    - List categories
│   ├── Create.cshtml.cs   - Add new category
│   ├── Edit.cshtml.cs     - Update category
│   └── Delete.cshtml.cs   - Remove category
├── Products/
│   └── [Same CRUD structure]
├── Customers/
│   └── [Same CRUD structure]
├── PayModes/
│   └── [Same CRUD structure]
└── Account/
    ├── Login.cshtml.cs    - User authentication
    ├── Register.cshtml.cs - User registration
    └── Logout.cshtml.cs   - User logout

Best Practices

  1. Use async/await: All database operations should be asynchronous
  2. Validate input: Always check ModelState before processing POST data
  3. Handle null cases: Check for null DbSets and parameters
  4. Use PRG pattern: Redirect after POST to prevent duplicate submissions
  5. Return appropriate results: Use NotFound() for missing resources
  6. Follow naming conventions: OnGetAsync, OnPostAsync, etc.
  7. Keep logic thin: Complex business logic should be in services, not PageModels

Build docs developers (and LLMs) love