Appearance
Are you an LLM? You can read better optimized documentation at /views/page_views.md for this page in Markdown format
Page Views
This document provides a technical overview of the primary Razor page views within the TheExampleApp application. It is intended for developers responsible for maintaining and extending the application's user interface. Each section details a specific .cshtml file, its purpose, data binding, and key implementation patterns.
For a higher-level overview of the view architecture, refer to the Razor Pages Architecture documentation.
Index.cshtml
This view serves as the application's home page. It is designed as a flexible container for a dynamic set of content modules fetched from the Contentful CMS.
File Location: TheExampleApp/Pages/Index.cshtml
Implementation Details
The core responsibility of Index.cshtml is to iterate over a collection of content modules and render the appropriate partial view for each. This pattern allows content editors to compose the home page layout in Contentful without requiring code changes.
- Dynamic Module Rendering: The view uses a
foreachloop overModel.IndexPage.ContentModules. The type of each module determines which partial view is rendered. This is achieved with@Html.Partial(module.GetType().Name, module). For this to work, a partial view must exist with a name matching the C# class name of the module (e.g.,HeroImage.csmaps toHeroImage.cshtml). - Editorial Features: A View Component,
<vc:editorial-features>, is included to provide direct links to edit the corresponding Contentful entries, visible only when editorial features are enabled. - Edge Case Handling: If
Model.IndexPage.ContentModulesis null or empty, theNoContent.cshtmlpartial view is rendered to provide a user-friendly message.
csharp
// TheExampleApp/Pages/Index.cshtml
@page
@model IndexModel
@{
ViewData["ActivePage"] = "Home";
ViewData["Title"] = "Home";
}
// Renders edit links for the page entry in Contentful
<div class="layout-centered">
<vc:editorial-features sys="@Model.SystemProperties"></vc:editorial-features>
</div>
<div class="modules-container">
@if (Model.IndexPage.ContentModules == null || Model.IndexPage.ContentModules.Count == 0)
{
// Fallback for when no content modules are defined in Contentful
@Html.Partial("NoContent")
}
else
{
// Dynamically renders a partial view for each module based on its type
@foreach (var module in Model.IndexPage.ContentModules)
{
@Html.Partial(module.GetType().Name, module)
}
}
</div>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
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
For more information on the partials used here, see Shared Views.
Courses.cshtml
This view displays a list of all available courses. It features a sidebar for filtering by category and a main content area showing course cards.
File Location: TheExampleApp/Pages/Courses.cshtml
Implementation Details
The page uses a two-column sidebar layout (layout-sidebar).
- Breadcrumbs: The page includes a breadcrumb navigation component rendered via the
Breadcrumbspartial view. - Category Sidebar: The left sidebar lists all course categories fetched from Contentful. It dynamically generates links for each category. The "All Courses" link is always shown first. The "active" state of a link is determined by comparing the current
Model.SelectedCategorywith the category in the loop. - Course Grid: The main content area displays the courses in a grid. It iterates through
Model.Coursesand renders aCourseCard.cshtmlpartial view for each course. - Editorial Link: When editorial features are enabled, a prominent link is displayed that takes the user directly to the "Course" content type view within the Contentful web app, streamlining the content editing workflow. The space ID is extracted from the first category's system properties.
csharp
// TheExampleApp/Pages/Courses.cshtml
// ... sidebar logic ...
<section class="layout-sidebar__content">
<div class="courses">
// Title changes based on whether a category is selected
<h1>@(Model.SelectedCategory != null ? Model.SelectedCategory.Title : Localizer["allCoursesLabel"].Value) (@Model.Courses?.Count)</h1>
// Link to edit content in the Contentful web app
@if (Model.EditorialFeaturesEnabled)
{
<div class="editorial-features">
<a ... href="@($\"https://app.contentful.com/spaces/{...}/entries/?contentTypeId=course\")">
@Localizer["editInTheWebAppLabel"]
</a>
...
</div>
}
<div class="grid-list">
@if (Model.Courses == null || Model.Courses.Count == 0)
{
@Html.Partial("NoContent")
}
else
{
// Renders a card for each course in the model
@foreach (var course in Model.Courses)
{
<div class="grid-list__item">
@Html.Partial("CourseCard", course)
</div>
}
}
</div>
</div>
</section>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
35
36
37
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
35
36
37
Further details on course data models can be found in the Course Management documentation.
Courses/Index.cshtml
This view renders the detail page for a single course. It is a route-based page that uses the course's slug as a parameter.
File Location: TheExampleApp/Pages/Courses/Index.cshtmlRoute: /courses/{slug}
Implementation Details
This page uses a two-section layout: breadcrumbs in a centered layout, followed by the main layout-sidebar structure.
- Breadcrumbs: The page includes a breadcrumb navigation component rendered in a
layout-no-sidebarcontainer at the top of the page. - Table of Contents: The sidebar is populated by the
TableOfContentspartial view, which displays the list of lessons for the current course. See Shared Views for more on this partial. - Editorial Features: A View Component,
<vc:editorial-features>, is included after the course title to provide direct links to edit the course entry in Contentful. - Course Overview: The main content area displays a structured overview section with key course information:
- Duration: Displayed with an icon and localized label showing the course length in minutes (only rendered if duration data exists).
- Skill Level: Displayed with an icon and localized label indicating the course difficulty (only rendered if skill level data exists).
- Start Course CTA: A "Start Course" button is conditionally displayed only when the course has lessons. It links to the first lesson in the course, demonstrating how to construct nested routes.
- Markdown Rendering: The course description, fetched from a Markdown field in Contentful, is rendered to HTML using the
<markdown>Tag Helper. This helper is powered by the Markdig library.
csharp
// TheExampleApp/Pages/Courses/Index.cshtml
@page "{slug}"
@model TheExampleApp.Pages.Courses.IndexModel
<div class="layout-no-sidebar">@Html.Partial("Breadcrumbs")</div>
<div class="layout-sidebar">
@Html.Partial("TableOfContents", Model.Course)
<section class="layout-sidebar__content">
<div class="course">
<h1 class="course__title">@Model.Course?.Title</h1>
<vc:editorial-features sys="@new [] { Model.Course?.Sys }"></vc:editorial-features>
<div class="course__overview">
<h3 class="course__overview-title">@Localizer["overviewLabel"]</h3>
// Duration and skill level sections with icons
// ...
@if (Model.Course != null && Model.Course.Lessons != null && Model.Course.Lessons.Any())
{
<div class="course__overview-cta-wrapper">
// Only rendered if the course has lessons
<a class="course__overview-cta cta" href="/courses/@Model.Course?.Slug/lessons/@Model.Course?.Lessons?.FirstOrDefault()?.Slug">@Localizer["startCourseLabel"]</a>
</div>
}
</div>
// Renders Markdown content from the model to HTML
<div class="course-description"><markdown content="@Model.Course.Description" /></div>
</div>
</section>
</div>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
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
Courses/Lessons.cshtml
This view is responsible for displaying the content of an individual lesson within a course.
File Location: TheExampleApp/Pages/Courses/Lessons.cshtmlRoute: /courses/{slug}/{lessonSlug}
Implementation Details
This page uses a two-section layout: breadcrumbs in a centered layout, followed by the main layout-sidebar structure.
- Breadcrumbs: The page includes a breadcrumb navigation component rendered in a
layout-no-sidebarcontainer at the top of the page. - Table of Contents: The sidebar is populated by the
TableOfContentspartial view. - Active Lesson State: This view uses the
TableOfContentspartial, but passes the current lesson's slug viaViewData. This allows the partial to highlight the currently active lesson in the navigation list. This is a key pattern for maintaining state in shared UI components. - Editorial Features: A View Component,
<vc:editorial-features>, is included after the lesson title to provide direct links to edit the lesson entry in Contentful. - Dynamic Lesson Content: Similar to
Index.cshtml, the lesson's content is composed of dynamic modules. The view iterates throughModel.SelectedLesson.Modulesand renders the corresponding partial for each module type. - Navigation: A "Next Lesson" button is conditionally rendered if
Model.NextLessonSlugis not null, providing a linear progression through the course.
csharp
// TheExampleApp/Pages/Courses/Lessons.cshtml
@page "{slug}/{lessonSlug}"
@model TheExampleApp.Pages.Courses.LessonsModel
<div class="layout-no-sidebar">@Html.Partial("Breadcrumbs")</div>
<div class="layout-sidebar">
// Pass the current lesson slug to the partial view to highlight the active item
@Html.Partial("TableOfContents", Model.Course, new ViewDataDictionary(this.ViewData) { { "slug", Model.SelectedLesson.Slug } })
<section class="layout-sidebar__content">
<div class="lesson">
<h1 class="lesson__title">@Model.SelectedLesson.Title</h1>
<vc:editorial-features sys="@Model.SystemProperties"></vc:editorial-features>
<div class="lesson__modules">
@if (Model.SelectedLesson.Modules == null || Model.SelectedLesson.Modules.Count == 0)
{
@Html.Partial("NoContent")
}
else
{
@foreach (var module in Model.SelectedLesson.Modules)
{
@Html.Partial(module.GetType().Name, module)
}
}
</div>
// Conditionally render the "Next Lesson" button
@if (!string.IsNullOrEmpty(Model.NextLessonSlug))
{
<a class="lesson__cta cta" href="/courses/@Model.Course.Slug/lessons/@Model.NextLessonSlug">@Localizer["nextLessonLabel"]</a>
}
</div>
</section>
</div>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
35
36
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
35
36
Settings.cshtml
This is a utility page that allows a user to configure the Contentful API credentials for their session, overriding the default values from appsettings.json. This is primarily a developer-focused feature for testing against different Contentful spaces.
File Location: TheExampleApp/Pages/Settings.cshtmlRoute: /Settings/{handler?}
Implementation Details
- Form Handling: The page contains a form that
POSTs to its ownPageModel. It uses ASP.NET Core Tag Helpers (asp-for,asp-validation-for) for model binding and validation. - Named Handlers: The page supports optional handler routing with
@page "{handler?}". A "Reset Credentials" button is implemented using a separate form that posts to a named handler method (/Settings?handler=ResetCredentials) in thePageModel. - State Display: The view includes detailed logic to display the current API credential status:
- It indicates whether it's using default credentials from
appsettings.jsonor custom credentials stored in the user's session. - It displays the connected Space Name and ID.
- When using session credentials, it provides a shareable URL that pre-fills the settings form via query parameters.
- It indicates whether it's using default credentials from
- Feedback: It uses
TempDatato display success or error messages after a form submission, a common pattern for Post/Redirect/Get (PRG) scenarios. - Form Fields: The settings form includes four main fields:
- Space ID: Required field for the Contentful space identifier.
- CDA Access Token: Required field for the Content Delivery API access token.
- CPA Access Token: Required field for the Content Preview API access token.
- Enable Editorial Features: Checkbox to enable/disable editorial features in the application.
csharp
// TheExampleApp/Pages/Settings.cshtml
@page "{handler?}"
@model TheExampleApp.Pages.SettingsModel
// ...
// Display success/error messages from TempData after a POST
@if (TempData["Success"] != null) { /* ... success message ... */ }
@if (TempData["Invalid"] != null) { /* ... error message ... */ }
// Display current connection status
@if (!string.IsNullOrEmpty(Model.SpaceName)) { /* ... status block ... */ }
// Main settings form
<form method="post" class="form">
<div class="form-item">
<label for="input-space-id">@Localizer["spaceIdLabel"]</label>
<input asp-for="@Model.AppOptions.SpaceId" id="input-space-id" required />
// ... validation message display ...
</div>
<div class="form-item">
<label for="input-delivery-token">@Localizer["cdaAccessTokenLabel"]</label>
<input asp-for="@Model.AppOptions.AccessToken" id="input-delivery-token" required />
// ... validation message display ...
</div>
<div class="form-item">
<label for="input-preview-token">@Localizer["cpaAccessTokenLabel"]</label>
<input asp-for="@Model.AppOptions.PreviewToken" id="input-preview-token" required />
// ... validation message display ...
</div>
<div class="form-item">
<input asp-for="@Model.AppOptions.EnableEditorialFeatures" id="input-editorial-features" />
<label for="input-editorial-features">@Localizer["enableEditorialFeaturesLabel"]</label>
</div>
<div class="form-item">
<input class="cta" type="submit" value="@Localizer["saveSettingsButtonLabel"]" />
</div>
</form>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
35
36
37
38
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
35
36
37
38
Error.cshtml
This is the standard error handling page for the application. Its source was not provided, but it follows the conventional ASP.NET Core template.
Assumed Implementation
- Model: Binds to an
ErrorViewModelwhich typically containsRequestId. - Functionality: Displays a generic error message to the user. For development environments, it will display the
RequestIdto aid in debugging by correlating with server logs. - Location:
TheExampleApp/Pages/Error.cshtml.
Imprint.cshtml
This view is intended for displaying static legal information, such as a company imprint or privacy policy. Its source was not provided.
Assumed Implementation
- Content Source: The content for this page is likely fetched from a specific, single entry in Contentful (e.g., an entry of a "Legal Page" content type).
- Structure: The page will have a simple layout, likely rendering a title and a rich text or Markdown field from the fetched Contentful entry.
- Location:
TheExampleApp/Pages/Imprint.cshtml.
View-Model Binding
The application exclusively uses the ASP.NET Core Razor Pages pattern, which promotes a strong connection between a view and its corresponding PageModel class.
- Convention: Each
.cshtmlview is backed by a.cshtml.csfile containing aPageModelclass (e.g.,Courses.cshtmlis backed byCoursesModel). - Data Access: The view accesses all its data through the
@Modelproperty, which is an instance of its correspondingPageModel. ThePageModelis responsible for all data fetching and business logic, keeping the view clean and focused on presentation. - Lifecycle: Typically, the
OnGetAsync()method in thePageModelis executed on aGETrequest. This method fetches data from services (like the Contentful client), populates the properties of thePageModelinstance, and then the Razor engine renders the view using that populated model.
The following snippet from Courses.cshtml illustrates this pattern clearly. All data (Title, Courses, Categories, SelectedCategory) is accessed directly from the @Model object.
csharp
// TheExampleApp/Pages/Courses.cshtml
@page
@model TheExampleApp.Pages.CoursesModel // Defines the type of @Model
@{
// Accessing a property on the PageModel to set ViewData
ViewData["Title"] = $"{Localizer["allCoursesLabel"].Value} ({Model.Courses?.Count})";
}
// ...
// Iterating over a collection property of the PageModel
@foreach (var category in Model.Categories)
{
<li class="sidebar-menu__item">
// Using PageModel properties to determine UI state and construct URLs
<a class="sidebar-menu__link @(Model.SelectedCategory?.Slug == category.Slug ? "active" : "")" href="/courses/categories/@category.Slug">@category.Title</a>
</li>
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
For a more in-depth explanation of this architecture, please consult the Razor Pages Architecture documentation.