ASP.NET Core 8 Website - Razor Page Models
PART 7: Introducing Razor Page Models
PART 6 created a shared layout, but the Home page still calculated the server time inside Index.cshtml. This part separates C# page logic from HTML markup by introducing Razor Page Model files.
1. Understanding the Razor Page pair
A typical Razor Page consists of two related files:
| File | Responsibility |
|---|---|
Index.cshtml | Page structure and displayed content |
Index.cshtml.cs | C# properties, handlers and page logic |
The .cshtml.cs file is commonly called the code-behind or Page Model.
2. Create Pages/Index.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace basicdotnet.Pages;
public class IndexModel : PageModel
{
public string ServerTime { get; private set; } = "";
public void OnGet()
{
ServerTime = DateTime.Now.ToString("HH:mm:ss");
}
}
3. Understand IndexModel
The class inherits from PageModel:
public class IndexModel : PageModel
PageModel provides the features needed to process a Razor Page request.
The public property
public string ServerTime { get; private set; } = "";
The Razor page can read this property. The private setter prevents unrelated code from changing it directly.
The GET handler
public void OnGet()
{
ServerTime = DateTime.Now.ToString("HH:mm:ss");
}
OnGet() runs when the browser sends an HTTP GET request for the page. It calculates the server time and stores it in ServerTime.
4. Update Pages/Index.cshtml
Replace the file with:
@page
@model IndexModel
@{
ViewData["Title"] = "Home";
}
<main class="page-content home-content">
<h1>Welcome to basicdotnet</h1>
<p>
A simple ASP.NET Core web app built
with Razor Pages.
</p>
<p>
It's @Model.ServerTime on the server.
</p>
</main>
5. Understand @model
@model IndexModel
This directive connects Index.cshtml to the IndexModel class. The page accesses its public members through Model:
@Model.ServerTime
The server produces output similar to:
It's 18:42:15 on the server.
6. Create Pages/Privacy.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace basicdotnet.Pages;
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
The Privacy page has no special logic yet, but creating its Page Model gives it the standard Razor Pages structure and provides a place for future logic.
7. Update Pages/Privacy.cshtml
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<main class="page-content">
<h1>@ViewData["Title"]</h1>
<p>
basicdotnet is a demonstration application.
</p>
<p>
This website does not collect or store
personal information.
</p>
</main>
The addition is:
@model PrivacyModel
8. Create Pages/NotFound.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace basicdotnet.Pages;
public class NotFoundModel : PageModel
{
public void OnGet()
{
Response.StatusCode = StatusCodes.Status404NotFound;
}
}
The 404 status-setting logic now belongs in the Page Model rather than in the markup.
9. Update Pages/NotFound.cshtml
@page
@model NotFoundModel
@{
ViewData["Title"] = "Page Not Found";
}
<main class="page-content">
<h1 class="error-code">
404 - Page Not Found
</h1>
<p>
The requested page could not be found.
</p>
<p>
<a href="/">Return to the Home page</a>
</p>
</main>
10. Add shared Razor imports
Create Pages/_ViewImports.cshtml:
@using basicdotnet
@namespace basicdotnet.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
This file makes the project namespace and ASP.NET Core Tag Helpers available to all Razor Pages. It also allows pages to refer to models such as IndexModel without writing the full namespace.
11. Keep Program.cs unchanged
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.MapFallbackToPage("/NotFound");
app.Run();
Page Models do not need to be registered individually. AddRazorPages() and MapRazorPages() already enable their discovery and use.
12. Check the project structure
basicdotnet/
├── Pages/
│ ├── Shared/
│ │ └── _Layout.cshtml
│ ├── _ViewImports.cshtml
│ ├── _ViewStart.cshtml
│ ├── Index.cshtml
│ ├── Index.cshtml.cs
│ ├── NotFound.cshtml
│ ├── NotFound.cshtml.cs
│ ├── Privacy.cshtml
│ └── Privacy.cshtml.cs
├── wwwroot/
│ └── css/
│ └── site.css
├── basicdotnet.csproj
└── Program.cs
13. Understand the request flow
Browser requests /
↓
Razor Pages selects Index.cshtml
↓
IndexModel.OnGet() runs
↓
ServerTime receives the current time
↓
Index.cshtml reads @Model.ServerTime
↓
_Layout.cshtml produces the complete document
14. Build and run
dotnet build
dotnet run
Open the address shown in the terminal and test:
| URL | Expected result |
|---|---|
/ | Home page with server time |
/privacy | Privacy Policy page |
/contact | Shared-layout page with HTTP 404 status |
Razor markup versus Page Model
| Razor page | Page Model |
|---|---|
| HTML structure | C# request processing |
| Displays properties | Prepares property values |
Uses @Model | Inherits from PageModel |
| Focuses on presentation | Focuses on page behaviour |
What has improved?
- Server-time logic has been removed from the HTML file.
- Each page has a dedicated Page Model.
- The 404 status is assigned in C# rather than Razor markup.
- The project now follows the conventional Razor Pages file structure.
- Future form handling, validation and dependency injection can be added cleanly.
The page is still refreshed completely to obtain a new time. The next part will introduce a named Razor Page handler and use JavaScript to request the server time independently.
Next: PART 8 adds OnGetTime() and uses fetch() to update the server time without reloading the entire Home page.