ASP.NET Core  

Razor Pages in ASP.NET Core

Razor Pages is a page-based programming model introduced in ASP.NET Core 2.0 to simplify the process of building dynamic, server-side web applications. It’s built on top of the MVC (Model-View-Controller) pattern but provides a more streamlined, page-centric approachβ€”ideal for developers who prefer working with page-focused architectures rather than the traditional controller-action structure.

With Razor Pages, each web page is self-contained, consisting of both the HTML markup and the logic required to handle user input and data. This design makes it easier to build, test, and maintain modern web applications in ASP.NET Core.

What is Razor Pages?

Razor Pages uses the Razor syntax, a lightweight templating engine that combines C# and HTML. Each Razor Page typically has two files:

  1. A .cshtml file β€” contains HTML markup and Razor syntax.

  2. A .cshtml.cs file β€” known as the PageModel, containing the C# backend logic for handling page events.

This structure allows developers to keep UI and logic separate but closely related, improving organization and readability.

Example. HelloWorld Razor Page

Index.cshtml

@page
@model RazorPagesDemo.Pages.IndexModel
@{
    ViewData["Title"] = "Home Page";
}

<h1>Hello, @Model.Message!</h1>

Index.cshtml.cs

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesDemo.Pages
{
    public class IndexModel : PageModel
    {
        public string Message { get; private set; } = "Welcome to Razor Pages!";

        public void OnGet()
        {
            // Initialization logic here
        }
    }
}

Here:

  • The @page directive makes the .cshtml file a Razor Page.

  • The IndexModel class acts as the code-behind, handling page events.

  • The OnGet() method executes when the page is accessed via an HTTP GET request.

Project Setup for Razor Pages

Creating a Razor Pages project in Visual Studio or via CLI is simple.

Using .NET CLI

dotnet new webapp -n RazorPagesDemo
cd RazorPagesDemo
dotnet run

This command scaffolds a Razor Pages web application with the required folder structure.

Folder Structure

RazorPagesDemo/
β”‚
β”œβ”€β”€ Pages/
β”‚   β”œβ”€β”€ Index.cshtml
β”‚   β”œβ”€β”€ Index.cshtml.cs
β”‚   └── Shared/
β”‚       └── _Layout.cshtml
β”‚
β”œβ”€β”€ wwwroot/
β”œβ”€β”€ Program.cs
└── appsettings.json

All Razor Pages reside inside the Pages folder by default, but you can organize them into subfolders for modularity.

How Razor Pages Works

When an HTTP request is made:

  1. The ASP.NET Core Routing middleware maps the URL to a Razor Page.

  2. The page’s PageModel executes the appropriate handler method (OnGet, OnPost, etc.).

  3. The .cshtml view is rendered with the model’s data.

  4. The final HTML response is sent to the browser.

Unlike MVC, there’s no need to define separate controllers or route configurationsβ€”the framework handles it automatically.

Handler Methods in Razor Pages

Each Razor Page can respond to different HTTP verbs using page handler methods:

  • OnGet() β†’ Handles HTTP GET requests

  • OnPost() β†’ Handles HTTP POST requests

  • OnPut() β†’ Handles HTTP PUT requests

  • OnDelete() β†’ Handles HTTP DELETE requests

Example. Handling Form Submissions

Contact.cshtml

@page
@model RazorPagesDemo.Pages.ContactModel

<h2>Contact Us</h2>

<form method="post">
    <label>Name:</label>
    <input type="text" asp-for="Name" />
    <br/>
    <label>Message:</label>
    <textarea asp-for="Message"></textarea>
    <br/>
    <button type="submit">Submit</button>
</form>

@if (Model.IsSubmitted)
{
    <p>Thanks, @Model.Name! Your message was received.</p>
}

Contact.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesDemo.Pages
{
    public class ContactModel : PageModel
    {
        [BindProperty]
        public string Name { get; set; }
        [BindProperty]
        public string Message { get; set; }

        public bool IsSubmitted { get; set; } = false;

        public void OnGet() { }

        public void OnPost()
        {
            IsSubmitted = true;
        }
    }
}
  • The [BindProperty] attribute binds form inputs directly to C# properties.

  • OnPost() executes when the form is submitted.

  • The view updates dynamically after submission.

Routing in Razor Pages

Routing is automatic in Razor Pages. The path to the .cshtml file defines the URL route.

Example

  • Pages/About.cshtml β†’ /About

  • Pages/Products/Details.cshtml β†’ /Products/Details

However, you can customize routes using the @page directive:

@page "/custom-route"

You can also define route parameters:

@page "/products/{id:int}"

Details.cshtml.cs

public class DetailsModel : PageModel
{
    public int ProductId { get; private set; }

    public void OnGet(int id)
    {
        ProductId = id;
    }
}

Visiting /products/10 binds 10 to the id parameter automatically.

Using Model Binding and Validation

Razor Pages simplifies form handling using model binding and data annotations for validation.

Example. Registration Form

Register.cshtml

@page
@model RazorPagesDemo.Pages.RegisterModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h2>User Registration</h2>
<form method="post">
    <div>
        <label asp-for="User.Name"></label>
        <input asp-for="User.Name" />
        <span asp-validation-for="User.Name"></span>
    </div>

    <div>
        <label asp-for="User.Email"></label>
        <input asp-for="User.Email" />
        <span asp-validation-for="User.Email"></span>
    </div>

    <button type="submit">Register</button>
</form>

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

Register.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesDemo.Pages
{
    public class RegisterModel : PageModel
    {
        [BindProperty]
        public UserModel User { get; set; }

        public string Message { get; private set; }

        public void OnGet() { }

        public IActionResult OnPost()
        {
            if (!ModelState.IsValid)
                return Page();

            Message = $"User {User.Name} registered successfully!";
            return RedirectToPage("/Success");
        }

        public class UserModel
        {
            [Required]
            [StringLength(50)]
            public string Name { get; set; }

            [Required]
            [EmailAddress]
            public string Email { get; set; }
        }
    }
}

Here:

  • The [Required] and [EmailAddress] attributes perform server-side validation.

  • The asp-validation-for tag helper shows validation messages dynamically.

  • Client-side validation is automatically enabled using jQuery Unobtrusive Validation.

Dependency Injection in Razor Pages

Razor Pages fully supports dependency injection (DI) through the PageModel constructor.

Example: Injecting a Service

public interface IMessageService
{
    string GetWelcomeMessage();
}

public class MessageService : IMessageService
{
    public string GetWelcomeMessage() => "Hello from injected service!";
}

public class IndexModel : PageModel
{
    private readonly IMessageService _messageService;
    public string Message { get; private set; }

    public IndexModel(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public void OnGet()
    {
        Message = _messageService.GetWelcomeMessage();
    }
}

In Program.cs:

builder.Services.AddScoped<IMessageService, MessageService>();

This demonstrates how business logic can be separated cleanly from the UI layer.

Layouts and Partial Views

Razor Pages supports Layouts, Partial Views, and Tag Helpers just like MVC.

  • _Layout.cshtml defines common site structure (header, footer, navigation).

  • Partial views like _LoginPartial.cshtml can be reused across pages.

Example Layout usage

@{
    Layout = "_Layout";
}

Razor Syntax Overview

Razor syntax blends C# and HTML seamlessly:

@{
    var name = "Dinesh";
    var date = DateTime.Now;
}
<p>Hello, @name! Today’s date is @date.ToShortDateString().</p>

@if (date.DayOfWeek == DayOfWeek.Sunday)
{
    <p>It’s Sunday! Enjoy your weekend!</p>
}

The @ symbol tells the compiler to switch from HTML to C# context, making Razor concise and expressive.

Advantages of Razor Pages

  1. Simplicity – Easier for beginners compared to MVC.

  2. Separation of concerns – UI and backend logic are in separate files.

  3. Better organization – Each page handles its own functionality.

  4. Lightweight and fast – Less boilerplate code than MVC controllers.

  5. Built-in security – Works seamlessly with ASP.NET Core Identity and CSRF protection.

  6. Testability – PageModel classes are easy to test with unit tests.

Real-world Use Cases

Razor Pages is ideal for:

  • Content-driven websites (blogs, portfolios, company sites)

  • CRUD applications

  • Admin dashboards

  • Internal business tools

  • Prototypes and small-scale SaaS frontends

Conclusion

Razor Pages in ASP.NET Core represents the evolution of web development in .NET β€” simpler, cleaner, and more maintainable than traditional MVC for many use cases.

It allows developers to focus on page-level functionality, reduces complexity, and integrates seamlessly with other ASP.NET Core features such as dependency injection, routing, model binding, and validation.

Whether you are a beginner or an experienced developer, Razor Pages provides a powerful and efficient way to build fast, secure, and scalable web applications in the modern .NET ecosystem.