Appearance
Are you an LLM? You can read better optimized documentation at /core_features/razor_pages.md for this page in Markdown format
Razor Pages
This document provides a detailed technical overview of the Razor Pages implementation within TheExampleApp. It covers the architecture, structure, key page models, and routing conventions used to build the server-rendered user interface.
Page model architecture
The application is built using the ASP.NET Core Razor Pages framework, which employs a Page Model architectural pattern. This pattern tightly couples a view (.cshtml) with its corresponding logic and request handling class (.cshtml.cs), promoting a page-focused organization.
BasePageModel Inheritance
All page models in the application inherit from a common BasePageModel. This design choice centralizes shared dependencies and functionality.
- Dependency Injection: The
BasePageModelconstructor requires anIContentfulClient, ensuring that every page has access to the Contentful service for data retrieval. - Common Logic: While the provided source for
BasePageModelis not included, it serves as the ideal location for any logic common to all pages, such as handling global layout data, managing session state, or implementing cross-cutting concerns.
Request Handling and Data Fetching
Each page model is responsible for handling HTTP requests for its corresponding page, primarily through OnGet and OnPost handler methods.
In this application, the OnGet method is used to:
- Construct a query for the Contentful API using the
Contentful.Core.Search.QueryBuilder. - Fetch content asynchronously using the injected
IContentfulClient. - Map the fetched content to public properties on the model.
The following example from Index.cshtml.cs demonstrates this flow, where the home page layout is fetched from Contentful based on its slug.
csharp
// File: TheExampleApp/Pages/Index.cshtml.cs
public class IndexModel : BasePageModel
{
// BasePageModel constructor handles injecting the _client
public IndexModel(IContentfulClient client) : base(client)
{
}
public Layout IndexPage { get; set; }
public IEnumerable<SystemProperties> SystemProperties { get; set; }
public async Task OnGet()
{
// 1. Build a specific query for the "home" layout
var queryBuilder = QueryBuilder<Layout>.New
.ContentTypeIs("layout")
.FieldEquals(f => f.Slug, "home")
.Include(4) // Include linked entries up to 4 levels deep
.LocaleIs(HttpContext?.Session?.GetString(Startup.LOCALE_KEY) ?? CultureInfo.CurrentCulture.ToString());
// 2. Fetch the entry from Contentful
var indexPage = (await _client.GetEntries(queryBuilder)).FirstOrDefault();
// 3. Map data to public properties for the view to access
IndexPage = indexPage;
var systemProperties = new List<SystemProperties> { indexPage.Sys };
if (indexPage.ContentModules != null && indexPage.ContentModules.Any())
{
systemProperties.AddRange(indexPage.ContentModules?.Select(c => c.Sys));
}
SystemProperties = systemProperties;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Page structure
Razor Pages enforce a clear and organized structure by convention. Each page consists of two primary files located within the /Pages directory. For more information on the overall project layout, see the Project Structure documentation.
.cshtmlView File: The Razor template responsible for rendering the HTML markup. It has access to the public properties of its corresponding page model via the@modeldirective. For more on views, see the Views documentation..cshtml.csCode-Behind File: The C# class containing the page model logic. It inherits fromPageModel(or, in this case, the customBasePageModel) and handles all server-side logic for the page.
For example, the home page is composed of:
Pages/Index.cshtml: The view template.Pages/Index.cshtml.cs: TheIndexModelclass for request handling and data fetching.
This structure is consistent across the application, including for nested pages like Pages/Courses/Lessons.cshtml and Pages/Courses/Lessons.cshtml.cs.
Key pages
The application's core functionality is delivered through a set of key pages, each with a distinct responsibility.
| Page File Path | Purpose | Key Logic & Data Handling - |
|---|---|---|
Pages/Index.cshtml | Home page | Fetches a Contentful entry of type layout with the slug home. Gathers system properties of the layout and its content modules for rendering. See Content Display for details on how content is rendered. - |
Pages/Courses.cshtml | Course listing page | Fetches all course entries (ordered by creation date, descending) and all category entries. If an optional category slug is present in the URL, it filters the course list to show only courses in that category. Returns a NotFound result for invalid categories, setting a localized error message via IViewLocalizer in TempData. Uses IBreadcrumbsManager to update the navigation path with the category title. Exposes an EditorialFeaturesEnabled property based on session state. - |
Pages/Courses/Index.cshtml | Individual course detail page | Fetches a single course entry based on its slug using a Contentful query that includes linked entries up to 5 levels deep. Uses IVisitedLessonsManager to mark the course as viewed and IBreadcrumbsManager to update breadcrumbs with the course title. Returns a NotFound result if the course slug is invalid, setting a localized error message via IViewLocalizer in TempData. The associated Contentful models for Course contain the lesson list. - |
Pages/Courses/Lessons.cshtml | Individual lesson viewer | Fetches the parent course by its slug (with linked entries up to 5 levels deep), then finds the specific lesson within the course's Lessons collection using the lessonSlug. This avoids a separate API call for the lesson. Returns a NotFound result if either the course or lesson is invalid, setting localized error messages via IViewLocalizer in TempData. Uses IBreadcrumbsManager to update breadcrumbs with both the course and lesson titles. Uses IVisitedLessonsManager to track the lesson as visited and adds the visited lessons list to ViewData. Collects SystemProperties from the lesson and its modules for rendering. Calculates the NextLessonSlug property for "next lesson" navigation by finding the lesson following the current one in the course's lesson sequence. |
Pages/Settings.cshtml | User settings page | Implementation not provided in source files. Likely handles user-specific settings, potentially related to locale preferences or editorial features stored in the session. - |
Pages/Error.cshtml | Error handling page | Configured in Startup.cs via app.UseExceptionHandler("/Error") and app.UseStatusCodePagesWithReExecute("/Error"). This page is displayed for unhandled exceptions and non-success status codes (e.g., 404). It can display custom messages passed via TempData. - |
Routing configuration
The application overrides the default file-based routing of Razor Pages to create more user-friendly and SEO-friendly URL structures. This customization is configured in the ConfigureServices method of Startup.cs.
Custom Route Conventions
The AddPageRoute convention is used to map a specific Razor Page to a custom URL pattern. This allows for decoupling the URL from the physical file path.
csharp
// File: TheExampleApp/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ... other services
services.AddMvc().AddRazorPagesOptions(
options => {
// Maps /Pages/Courses.cshtml to handle URLs like /Courses/Categories/development
options.Conventions.AddPageRoute("/Courses", "Courses/Categories/{category?}");
// Maps /Pages/Courses/Index.cshtml to handle URLs like /Courses/hello-world/lessons
options.Conventions.AddPageRoute("/Courses/Index", "Courses/{slug}/lessons");
// Maps /Pages/Courses/Lessons.cshtml to handle URLs like /Courses/hello-world/lessons/introduction
options.Conventions.AddPageRoute("/Courses/Lessons", "Courses/{slug}/lessons/{lessonSlug}");
});
services.AddSession(options => {
// IdleTimeout is set to a high value to conform to requirements for this particular application.
// In your application you should use an IdleTimeout that suits your application needs or stick to the default of 20 minutes.
options.IdleTimeout = TimeSpan.FromDays(2);
});
// ... other services
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Route Mapping Summary
The following table summarizes the custom route mappings:
| Physical Page File | Custom URL Pattern | Description |
|---|---|---|
/Pages/Courses.cshtml | Courses/Categories/{category?} | Lists all courses, optionally filtered by category |
/Pages/Courses/Index.cshtml | Courses/{slug}/lessons | Displays an individual course detail page |
/Pages/Courses/Lessons.cshtml | Courses/{slug}/lessons/{lessonSlug} | Displays an individual lesson within a course |
URL Rewrite Rules
The application also configures URL rewrite rules in the Configure method to handle legacy URL patterns:
csharp
// File: TheExampleApp/Startup.cs
var options = new RewriteOptions();
// Redirects /courses/{slug}/lessons (with trailing slash) to /courses/{slug}
options.AddRedirect("courses/(.*)/lessons$", "/courses/$1");
app.UseRewriter(options);1
2
3
4
5
6
2
3
4
5
6
This ensures that URLs ending with /lessons (without a specific lesson slug) are redirected to the course detail page.
Session Configuration
The application uses session state to persist user preferences and track visited lessons. Session configuration is essential for several page features:
- Storing the selected locale preference
- Tracking visited lessons via
IVisitedLessonsManager - Managing editorial features toggle state
The session is configured with an extended idle timeout of 2 days to maintain user state across visits (see Startup.cs:96-100).