Introduction to Razor Pages
Razor Pages is a page-focused framework for building dynamic, data-driven web applications with ASP.NET Core. It simplifies traditional MVC (Model-View-Controller) patterns by combining the controller and view components into a single page model.
What Are Razor Pages?
Razor Pages provide:
- A page-based programming model
- Simplified development workflow compared to MVC
- Built on ASP.NET Core
- Uses Razor syntax for server-side rendering
- Combines markup (HTML) with C# code
History and Evolution
- Introduced in ASP.NET Core 2.0 (2017)
- Designed to make web development more approachable
- Evolved alongside MVC in subsequent .NET Core releases
- Now a first-class citizen in ASP.NET Core
Key Benefits
- Simplified Architecture: Combines controller and view logic
- Organized Structure: Each page is self-contained
- Productivity: Less ceremony than MVC for page-focused apps
- Flexibility: Can be used alongside MVC in the same project
- Performance: Same high performance as ASP.NET Core MVC
Setting Up the Development Environment
Prerequisites
- .NET SDK: Latest stable version
- IDE Options:
- Visual Studio (Windows/Mac)
- Visual Studio Code (Cross-platform)
- JetBrains Rider (Cross-platform)
- Web Browser: Chrome, Edge, Firefox, etc.
Creating a New Razor Pages Project
Using Visual Studio
- File → New → Project
- Select "ASP.NET Core Web App"
- Choose project name and location
- In the configuration dialog:
- Select .NET version
- Choose "ASP.NET Core Web App" template
- Ensure "Razor Pages" is selected
- Click Create
Using .NET CLI
dotnet new webapp -o MyRazorApp
cd MyRazorApp
dotnet run
Project Structure
A new Razor Pages project contains:
MyRazorApp/
├─��� Pages/ # Contains Razor Pages
│ ├── Shared/ # Shared layouts and partials
│ ├── _ViewStart.cshtml
│ ├── _ViewImports.cshtml
│ └── Index.cshtml # Default page
├── wwwroot/ # Static files (CSS, JS, images)
├── appsettings.json # Configuration
├── Program.cs # Startup and configuration
└── MyRazorApp.csproj # Project file
Understanding Razor Pages Fundamentals
Basic Components
- Page File (.cshtml): Contains HTML markup with Razor syntax
- Page Model File (.cshtml.cs): Contains C# code (handler methods, properties)
- Layout Files: Define common structure for multiple pages
- Partial Views: Reusable UI components
The Page Model
The Page Model (also called code-behind) is a C# class that:
- Handles HTTP requests
- Contains business logic
- Manages page state
- Prepares data for the view
Example Index.cshtml.cs
:
public class IndexModel : PageModel
{
public void OnGet()
{
// Initialization logic for GET requests
}
public IActionResult OnPost()
{
// Handle form submissions
return Page();
}
}
Razor Syntax Basics
Razor syntax allows mixing HTML with C# code:
@page
@model IndexModel
<h1>Welcome, @Model.UserName!</h1>
@{
var currentDate = DateTime.Now;
}
<p>Today is: @currentDate.ToShortDateString()</p>
@if(Model.IsPremiumUser)
{
<div class="premium-banner">
Thank you for being a premium member!
</div>
}
Creating Your First Razor Page
Step-by-Step Guide
- Add a New Page:
- Right-click Pages folder → Add → Razor Page
- Choose "Razor Page" template
- Name it "Welcome.cshtml"
- Edit the Page Model (Welcome.cshtml.cs):
public class WelcomeModel : PageModel { [BindProperty] public string Name { get; set; } public string Greeting { get; private set; } public void OnGet() { // Runs on initial page load } public IActionResult OnPost() { Greeting = $"Hello, {Name}! Welcome to our site."; return Page(); } }
- Edit the View (Welcome.cshtml):
@page @model WelcomeModel <h1>Welcome Page</h1> <form method="post"> <div class="form-group"> <label asp-for="Name"></label> <input asp-for="Name" class="form-control" /> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> @if(!string.IsNullOrEmpty(Model.Greeting)) { <div class="alert alert-success mt-3"> @Model.Greeting </div> }
Understanding the @page Directive
The @page
directive:
- Must be the first directive in a Razor Page
- Makes the file into an MVC action
- Routes requests directly to the page
- Can include route parameters:
@page "{id:int}"
Handler Methods
Razor Pages use convention-based handler methods:
OnGet()
: Handles HTTP GET requestsOnPost()
: Handles HTTP POST requestsOnGetAsync()
: Async version of OnGetOnPostAsync()
: Async version of OnPost- Custom handlers:
OnPost[Action]()
Routing in Razor Pages
Default Routing
By default, Razor Pages use a file-based routing system:
Pages/Index.cshtml
→/
Pages/About.cshtml
→/About
Pages/Products/Index.cshtml
→/Products
Pages/Products/Details.cshtml
→/Products/Details
Customizing Routes
You can customize routes using:
- Route Templates in the
@page
directive:@page "/about-us"
- Route Parameters:
@page "/products/{id:int}"
- Convention-based routing in
Program.cs
:builder.Services.AddRazorPages(options => { options.Conventions.AddPageRoute("/About", "/about-us"); });
Route Constraints
Common route constraints:
{id:int}
: Must be an integer{id:guid}
: Must be a GUID{name:alpha}
: Alphabetic characters only{slug:regex(^[a-z0-9_-]+$)}
: Custom regex
Accessing Route Data
In the Page Model:
public void OnGet(int id)
{
// id comes from route parameter
}
// Or using RouteData
public void OnGet()
{
var id = RouteData.Values["id"];
}
Working with Data
Model Binding
Razor Pages automatically bind form data to properties:
public class ContactModel : PageModel
{
[BindProperty]
public ContactForm Input { get; set; }
public class ContactForm
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
public string Message { get; set; }
}
public IActionResult OnPost()
{
if(!ModelState.IsValid)
{
return Page();
}
// Process the form
return RedirectToPage("/ThankYou");
}
}
Data Access Options
- Entity Framework Core (Recommended for most applications)
- Dapper (For lightweight data access)
- ADO.NET (For full control)
- Other ORMs (NHibernate, etc.)
- Web APIs (Client-side data access)
Example with EF Core
- Add NuGet package:
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- Create DbContext:
public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } } public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }
- Register in Program.cs:
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
- Use in Page Model:
public class ProductsModel : PageModel { private readonly AppDbContext _context; public ProductsModel(AppDbContext context) { _context = context; } public List<Product> Products { get; set; } public async Task OnGetAsync() { Products = await _context.Products.ToListAsync(); } }
Forms and Validation
Creating Forms
Razor Pages provides tag helpers for form creation:
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name" class="control-label"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Email" class="control-label"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Validation
ASP.NET Core provides built-in validation attributes:
public class ContactForm
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
public string Phone { get; set; }
[Range(1, 5)]
public int Rating { get; set; }
[DataType(DataType.MultilineText)]
public string Comments { get; set; }
}
Client-Side Validation
Automatically enabled when:
- jQuery Validation is included
- Model has validation attributes
- Form uses tag helpers
Required scripts (in _Layout.cshtml
):
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
Layouts and Partial Views
Layout Pages
Define common structure in Pages/Shared/_Layout.cshtml
:
<!DOCTYPE html>
<html>
<head>
<title>@ViewData["Title"] - My App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body>
<header>
<!-- Navigation -->
</header>
<main>
@RenderBody()
</main>
<footer>
<!-- Footer content -->
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Using Layouts
Set layout in _ViewStart.cshtml
:
@{
Layout = "_Layout";
}
Or override per page:
@{
Layout = "_AlternateLayout";
}
Sections
Define sections in layout:
@section Scripts {
<script>
// Page-specific scripts
</script>
}
Partial Views
Reusable components in Pages/Shared/
or Pages/[Folder]/
:
- Create
_LoginPartial.cshtml
:@if(User.Identity.IsAuthenticated) { <form asp-page="/Logout" method="post"> <button type="submit">Logout</button> </form> } else { <a asp-page="/Login">Login</a> }
- Include in layout or page:
<partial name="_LoginPartial" />
Dependency Injection in Razor Pages
Built-in DI Container
ASP.NET Core includes a built-in DI container. Common services:
- Configuration:
IConfiguration
- Logging:
ILogger<T>
- Database:
DbContext
- Authentication:
IAuthenticationService
- Email:
IEmailSender
Registering Services
In Program.cs
: