Appearance
Are you an LLM? You can read better optimized documentation at /api_reference/page_models.md for this page in Markdown format
Page models reference
This document provides a detailed technical reference for the Razor Page Models used in the TheExampleApp application. Page Models are C# classes that encapsulate the logic and data for their corresponding Razor Pages. They are responsible for handling HTTP requests, fetching data from the Contentful CMS, and preparing data for rendering in the view.
This application follows the standard ASP.NET Core Razor Pages architecture. For a general overview of this pattern, please refer to the Core Features: Razor Pages documentation. All page models inherit from a common BasePageModel, which likely provides shared functionality, such as the injected IContentfulClient.
Page model classes
The following sections detail each specific Page Model, its purpose, dependencies, and implementation logic.
IndexModel
- File:
TheExampleApp/Pages/Index.cshtml.cs - Route:
/ - Purpose: Serves as the backend logic for the application's home page.
The IndexModel is responsible for fetching the main layout structure for the home page from Contentful.
Logic
The OnGet handler is an asynchronous method that executes a query against the Contentful API to retrieve a single entry of the content type layout where the slug field is "home". It uses the contentful.aspnetcore SDK's QueryBuilder for a type-safe query.
Key implementation details:
- Localization: The query respects the current language setting by using
LocaleIs(), retrieving the locale from the user's session (HttpContext.Session), with a fallback toCultureInfo.CurrentCultureif no session locale is set. - Include Depth: It uses
.Include(4)to resolve linked entries up to 4 levels deep, ensuring that all necessary content modules are fetched in a single API call. - Data for Rendering: It exposes the fetched
Layoutobject and a collection ofSystemPropertiesto the view. TheSystemPropertiescollection includes the layout's system properties and those of all its content modules. These properties are used by view components to enable editorial features, such as the "Edit in Contentful" links.
Properties
public Layout IndexPage { get; set; }: Holds the fetchedLayoutentry from Contentful. See the API Models Reference for the structure of theLayoutclass.public IEnumerable<SystemProperties> SystemProperties { get; set; }: A collection of system properties for the main layout and its nested content modules.
CoursesModel
- File:
TheExampleApp/Pages/Courses.cshtml.cs - Route:
/coursesand/courses?category={slug} - Purpose: Displays a list of all available courses, with an option to filter by category.
This model manages the course catalog page. It fetches all courses and categories and performs filtering based on a URL query parameter.
Dependencies
IContentfulClient: For fetching data from Contentful.IBreadcrumbsManager: A custom service to dynamically update the page's breadcrumb navigation.IViewLocalizer: For accessing localized string resources, used for error messages.
Logic
The OnGet(string category) handler contains the core logic:
- It fetches all
categoryentries to populate the filtering UI. - It fetches all
courseentries, ordered by creation date. - If the optional
categoryparameter is present, it filters theCourseslist to include only those matching the category slug. - Error Handling: If a
categoryslug is provided but does not correspond to an existing category, the model sets aTempData["NotFound"]message and returns aNotFoundResult(HTTP 404). This is a crucial pattern for handling invalid slugs gracefully. - Breadcrumbs: It interacts with the
IBreadcrumbsManagerto replace the generic slug in the breadcrumb trail with the more user-friendly categoryTitle.
csharp
// TheExampleApp/Pages/Courses.cshtml.cs
public async Task<IActionResult> OnGet(string category)
{
// ... fetch categories and courses ...
if (!string.IsNullOrEmpty(category))
{
// Filter the courses by the selected category.
Courses = courses.Where(c => c.Categories != null && c.Categories.Any(x => x.Slug == category.ToLower())).ToList();
var cat = Categories.FirstOrDefault(c => c.Slug == category.ToLower());
if(cat == null)
{
// If the category doesn't exist, return a 404.
TempData["NotFound"] = _localizer["errorMessage404Category"].Value;
return NotFound();
}
// Update the breadcrumb with the category's title.
_breadcrumbsManager.ReplaceCrumbForSlug(category.ToLower().Replace('-', ' '), cat.Title);
}
else
{
// If we don't have a category, just display all courses.
Courses = courses.ToList();
}
return Page();
}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
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
Properties
public Category SelectedCategory { get; set; }: TheCategoryobject that matches the current filter, if any.public List<Course> Courses { get; set; }: The list of courses to be displayed on the page.public List<Category> Categories { get; set; }: The complete list of all categories, used to render the filter navigation.public bool EditorialFeaturesEnabled => HttpContext.Session.GetString("EditorialFeatures") == "Enabled": A computed property that checks the session to determine if editorial features should be enabled.
Courses/IndexModel
- File:
TheExampleApp/Pages/Courses/Index.cshtml.cs - Route:
/courses/{slug} - Purpose: Displays the details for a single course.
This model is responsible for fetching and displaying an individual course page, which typically lists the lessons within that course.
Dependencies
IVisitedLessonsManager: A custom service to track which courses and lessons a user has viewed.IBreadcrumbsManager: To update the breadcrumb trail.IViewLocalizer: For localized error messages.
Logic
The OnGet(string slug) handler fetches a single course from Contentful using its slug.
Key implementation details:
- Localization: The query respects the current language setting by using
LocaleIs(), retrieving the locale from the user's session (HttpContext.Session), with a fallback toCultureInfo.CurrentCultureif no session locale is set. - Include Depth: It uses
.Include(5)to resolve linked entries up to 5 levels deep, ensuring that nested course content (such as lessons and their modules) is fetched in a single API call. - Error Handling: If no course matches the provided
slug, it sets aTempData["NotFound"]message with a localized error string and returns aNotFoundResult(HTTP 404). - User Tracking: It calls
_visitedLessonsManager.AddVisitedLesson(Course.Sys.Id)to register that the user has viewed this course. The manager's state is then passed to the view viaViewData["VisitedLessons"]to visually indicate progress (e.g., checkmarks next to visited lessons). - Breadcrumbs: It updates the breadcrumb trail with the course's actual title using
_breadcrumbsManager.ReplaceCrumbForSlug(), transforming the slug into a user-friendly label.
Properties
public Course Course { get; set; }: The fetchedCourseobject to be displayed. See the API Models Reference for the structure of theCourseclass.
Courses/LessonsModel
- File:
TheExampleApp/Pages/Courses/Lessons.cshtml.cs - Route:
/courses/{slug}/lessons/{lessonSlug} - Purpose: Displays the content of a single lesson within a course.
This is the model for the lesson viewer page. It fetches the parent course and then identifies the specific lesson to display.
Dependencies
IContentfulClient: For fetching data from Contentful.IVisitedLessonsManager: A custom service to track which courses and lessons a user has viewed.IBreadcrumbsManager: To update the breadcrumb trail.IViewLocalizer: For localized error messages.
Logic
The OnGet(string slug, string lessonSlug) handler uses two route parameters to locate the correct content.
Key implementation details:
- Course Fetching: It first fetches the parent
Courseusing theslug, with.Include(5)to resolve linked entries up to 5 levels deep. - Localization: The query respects the current language setting by using
LocaleIs(), retrieving the locale from the user's session (HttpContext.Session), with a fallback toCultureInfo.CurrentCultureif no session locale is set. - Lesson Selection: It then searches within the
Course.Lessonscollection for aLessonthat matches thelessonSlug. - Error Handling: It includes robust checks and returns a
NotFoundResultwith localized error messages if either the course or the lesson cannot be found. - Breadcrumbs: It updates the breadcrumb trail for both the course and the lesson using
_breadcrumbsManager.ReplaceCrumbForSlug(), transforming the slugs into user-friendly titles. - User Tracking: It calls
_visitedLessonsManager.AddVisitedLesson(SelectedLesson.Sys.Id)to register that the user has viewed this specific lesson. The manager's state is then passed to the view viaViewData["VisitedLessons"]to visually indicate progress. - System Properties: It builds a collection of
SystemPropertiesthat includes the lesson's system properties and those of all its modules, used for editorial features.
csharp
// TheExampleApp/Pages/Courses/Lessons.cshtml.cs
// ... inside OnGet ...
SelectedLesson = Course.Lessons.FirstOrDefault(c => c.Slug == lessonSlug?.ToLower());
if (SelectedLesson == null)
{
// If the lesson is not found, also return a 404.
TempData["NotFound"] = _localizer["errorMessage404Lesson"].Value;
return NotFound();
}
// ...
// Add the current lesson as visited.
_visitedLessonsManager.AddVisitedLesson(SelectedLesson.Sys.Id);
// Add the visited lessons and courses to viewdata.
ViewData["VisitedLessons"] = _visitedLessonsManager.VisitedLessons;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Properties
public Course Course { get; set; }: The parent course of the lesson.public Lesson SelectedLesson { get; set; }: The specificLessonobject to be displayed.public IEnumerable<SystemProperties> SystemProperties { get; set; }: System properties for the lesson and its modules, used for editorial features.public string NextLessonSlug { get => ... }: A computed property that finds the slug of the next lesson in the course sequence, returningnullif the current lesson is the last one.
SettingsModel
- File:
TheExampleApp/Pages/Settings.cshtml.cs - Route:
/settings - Purpose: Allows developers to configure the Contentful Space ID and API tokens used by the application at runtime.
This is a more complex model with multiple handlers for GET and POST requests, managing application configuration stored in the user's session.
Dependencies
IContentfulOptionsManager: A custom service that manages the Contentful configuration for the application.IContentfulClient: For fetching data from Contentful.
Logic & Handlers
OnGet(): Prepares the settings page asynchronously. It retrieves any validation errors from a previous failed POST attempt (stored in the session) and populates the form with those errors and options if present. It also fetches the current space name from Contentful using_client.GetSpace(), populates theAppOptionswith current configuration values, and determines if custom credentials are being used via_manager.IsUsingCustomCredentials.OnGetInvalidCredentials(): A named GET handler that simply redirects back to the settings page.OnPost(SelectedOptions appOptions): Handles the main form submission. It validates the submitted options using theSelectedOptionsclass validation. If invalid, it redisplays the page with validation errors. If valid, it updates the editorial features setting in the session and serializes the new Contentful options into the session, then redirects back to the settings page with a success message.OnPostResetCredentials(): A named handler that clears any custom credentials from the session by setting an empty string for theContentfulOptionskey, reverting the application to its default configuration (likely fromappsettings.json).OnPostSwitchApi(string api, string prevPage): A named handler used by the API switcher dropdown. It updates theUsePreviewApiflag in the session while retaining all other options, and redirects the user back to the page they were on with anapiquery parameter.
SelectedOptions Validation
A key feature of the SettingsModel is its nested SelectedOptions class, which implements IValidatableObject for advanced, server-side validation.
The Validate method performs the following checks:
- Ensures required fields (
SpaceId,AccessToken,PreviewToken) are not empty. - Live API Verification: If all fields are present, it creates a temporary
ContentfulClientand attempts to make aGetSpace()call against both the Delivery API and the Preview API. - It catches
ContentfulExceptionand checks theStatusCodeto provide specific error messages for invalid tokens (401) or an incorrect space ID (404).
This proactive validation prevents the application from entering a broken state due to misconfigured credentials.
csharp
// TheExampleApp/Pages/Settings.cshtml.cs - Inside SelectedOptions.Validate
// ...
try
{
// Attempts to connect to the Delivery API
var space = contentfulClient.GetSpace().Result;
}
catch (AggregateException ae)
{
ae.Handle((ce) => {
if (ce is ContentfulException)
{
if ((ce as ContentfulException).StatusCode == 401)
{
validationResult = new ValidationResult(localizer["deliveryKeyInvalidLabel"].Value, new[] { nameof(AccessToken) });
}
// ... other status code handling ...
return true;
}
return false;
});
}
// ... similar logic for the Preview API ...1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Properties
public ContentfulOptions Options { get; set; }: The current Contentful configuration options being used by the application, injected viaIContentfulOptionsManager.public SelectedOptions AppOptions { get; set; }: The options displayed in the settings form, editable by the user.public string SpaceName { get; set; }: The name of the currently connected Contentful space, fetched duringOnGet().public bool IsUsingCustomCredentials { get; set; }: Indicates whether the application is using custom credentials (from session) or default credentials (from configuration).
Other Models
- ErrorModel: Handles the display of error pages.
- ImprintModel: Handles the display of the site's imprint/legal notice page.
These models are standard and contain minimal logic, primarily serving to render their respective static or semi-static pages.
Methods
The Page Models in this application consistently use the following patterns and conventions defined by the ASP.NET Core Razor Pages framework.
OnGet/OnGetAsync handlers
These methods are the entry point for handling HTTP GET requests. Their primary responsibilities are:
- Accepting parameters from the route or query string (e.g.,
OnGet(string slug)). - Querying services (like
IContentfulClient) to fetch the necessary data. - Populating the public properties of the model class with this data.
- Performing business logic, such as filtering or user tracking.
- Returning an
IActionResult, typicallyPage()to render the view orNotFound()in case of an error.
OnPost/OnPostAsync handlers
These methods handle HTTP POST requests, typically from form submissions.
- Model Binding: They accept a model class as a parameter (e.g.,
OnPost(SelectedOptions appOptions)), which ASP.NET Core automatically populates with the form data. - Validation: They check
ModelState.IsValidto ensure the submitted data meets the defined validation rules. - PRG Pattern: They implement the Post-Redirect-Get (PRG) pattern by returning a
RedirectToPageResultafter successfully processing data. This prevents form re-submission on page refresh. - State Transfer: They use
TempDatato pass short-lived messages (e.g., "Settings saved successfully!") to the target page after a redirect. - Named Handlers: The application uses named handlers (e.g.,
OnPostResetCredentials) to manage multiple distinct POST actions on a single page. These are linked from a form using theasp-page-handlertag helper.
Properties and data binding
Public properties on the Page Model serve as the bridge to the Razor view (.cshtml file).
- Data Exposure: Any data fetched in an
OnGethandler is assigned to a public property, making it accessible for rendering in the view. - Computed Properties: The codebase makes effective use of C# expression-bodied properties for simple, derived data, as seen in
LessonsModel.NextLessonSlug. This keeps logic encapsulated within the model rather than cluttering the view.
csharp
// TheExampleApp/Pages/Courses/Lessons.cshtml.cs
/// <summary>
/// The slug of the next lesson of the course.
/// </summary>
public string NextLessonSlug { get => Course.Lessons.SkipWhile(x => x != SelectedLesson).Skip(1).FirstOrDefault()?.Slug; }1
2
3
4
5
6
2
3
4
5
6
This pattern provides a clean and efficient way to calculate values that depend on other model properties.