Appearance
Are you an LLM? You can read better optimized documentation at /views/course_views.md for this page in Markdown format
Course Views
This document provides a detailed technical overview of the Razor views responsible for rendering course and lesson content. It is intended for developers maintaining and extending the application's user-facing course features. The views are built using server-side rendering with ASP.NET Core Razor Pages and partial views.
Course Catalog View
The course catalog is the main entry point for users to browse available courses. It displays a grid of all courses and allows for filtering by category.
File: TheExampleApp/Pages/Courses.cshtml
Implementation Details
The view is implemented as a Razor Page and utilizes a two-column layout (layout-sidebar). The main content area lists the courses, while the sidebar provides category filtering.
- Data Model: The page is backed by
TheExampleApp.Pages.CoursesModel, which suppliesModel.Courses(a list ofCourseobjects),Model.Categories,Model.SelectedCategory, andModel.EditorialFeaturesEnabled. - Routing: The page is accessible at
/courses. When a category is selected, the URL changes to/courses/categories/{slug}, and the page is re-rendered with a filtered list of courses. - Page Title: The page dynamically sets
ViewData["Title"]to display the localized "allCoursesLabel" text along with the count of courses. - Breadcrumbs: The page includes a breadcrumbs navigation component rendered via the
Breadcrumbspartial view in alayout-no-sidebarsection at the top of the page. - Course Display: The view iterates through the
Model.Coursescollection. For each course, it renders theCourseCardshared partial view, promoting reusability and consistent styling. - Empty State: If no courses match the filter criteria or if there are no courses in the system, the
NoContentpartial view is rendered to provide a user-friendly message. - Dynamic Headings: The
<h1>heading dynamically updates to show the selected category name (or "All Courses") and the total count of displayed courses. - Editorial Features: When
Model.EditorialFeaturesEnabledis true, an editorial features section is displayed that includes a link to edit courses in the Contentful web app. The link dynamically constructs the URL using the Contentful space ID from the first category's system properties. This feature also includes an informational hint icon with a tooltip explaining the editorial features.
Code Snippet: Rendering Course Cards
The core logic for displaying the list of courses involves a loop that renders a partial view for each item. This pattern keeps the main view clean and delegates the presentation of a single course to the CourseCard component.
csharp
// File: TheExampleApp/Pages/Courses.cshtml
<div class="grid-list">
@if (Model.Courses == null || Model.Courses.Count == 0)
{
// Display a message when no courses are available
@Html.Partial("NoContent")
}
else
{
// Loop through the collection of courses
@foreach (var course in Model.Courses)
{
<div class="grid-list__item">
// Render the reusable CourseCard partial for each course
@Html.Partial("CourseCard", course)
</div>
}
}
</div>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
Developer Note: The
CoursesModelis responsible for fetching the appropriate courses from the Contentful CMS based on the route parameters. For more information on the data structures, see the Content Models documentation.
Course Detail View
This view presents a comprehensive overview of a single course, including its description, metadata, and a link to start the lessons.
File: TheExampleApp/Pages/Courses/Index.cshtml
Implementation Details
This is a dynamic Razor Page that uses a route parameter to identify the course to display.
- Routing: The page uses the route template
@page "{slug}", making it accessible at URLs like/courses/my-first-course. Theslugis used to fetch the corresponding course entry from Contentful. - Page Title: The page dynamically sets
ViewData["Title"]to the course title (Model.Course?.Title). - Breadcrumbs: The page includes a breadcrumbs navigation component rendered via the
Breadcrumbspartial view in alayout-no-sidebarsection at the top of the page. - Layout: It uses the
layout-sidebarpattern, where the main content displays the course details and the sidebar contains the Table of Contents component (passingModel.Courseas the model parameter). - Course Overview Section: Displays key course metadata with localized labels and icons:
- Duration: Shows the course duration in minutes if available, using
durationLabelandminutesLabellocalization keys. - Skill Level: Displays the skill level with a localized label (e.g.,
BeginnerLabel,IntermediateLabel). - Both fields use SVG icons from
/icons/icons.svg.
- Duration: Shows the course duration in minutes if available, using
- Content Rendering:
- The main course description, which is authored in Markdown in Contentful, is rendered to HTML using the
<markdown>Tag Helper. This helper leverages the Markdig library.
- The main course description, which is authored in Markdown in Contentful, is rendered to HTML using the
- Call to Action: A "Start Course" button is displayed, which links directly to the first lesson of the course (
/courses/{slug}/lessons/{first-lesson-slug}). This button is conditionally rendered only if the course has lessons (checksModel.Course.Lessons != null && Model.Course.Lessons.Any()).
Code Snippet: Displaying Course Metadata and Description
csharp
// File: TheExampleApp/Pages/Courses/Index.cshtml
<div class="course">
<h1 class="course__title">@Model.Course?.Title</h1>
// View component for Contentful editorial features (e.g., "Edit in web app")
<vc:editorial-features sys="@new [] { Model.Course?.Sys }"></vc:editorial-features>
<div class="course__overview">
<h3 class="course__overview-title">@Localizer["overviewLabel"]</h3>
// Duration metadata with icon
<div class="course__overview-item">
<svg class="course__overview-icon">
<use xlink:href="/icons/icons.svg#duration"></use>
</svg>
<div class="course__overview-value">
@if (Model?.Course?.Duration != null) {
@Localizer["durationLabel"]@:: @Model.Course?.Duration @Localizer["minutesLabel"]
}
</div>
</div>
// Skill level metadata with icon
<div class="course__overview-item">
<svg class="course__overview-icon">
<use xlink:href="/icons/icons.svg#skill-level"></use>
</svg>
<div class="course__overview-value">
@if (Model?.Course?.SkillLevel != null) {
@Localizer["skillLevelLabel"]@:: @Localizer[$"{@Model.Course?.SkillLevel}Label"]
}
</div>
</div>
// Conditionally render Start Course button
@if (Model.Course != null && Model.Course.Lessons != null && Model.Course.Lessons.Any())
{
<div class="course__overview-cta-wrapper">
<a class="course__overview-cta cta" href="/courses/@Model.Course?.Slug/lessons/@Model.Course?.Lessons?.FirstOrDefault()?.Slug">
@Localizer["startCourseLabel"]
</a>
</div>
}
</div>
// The Markdig-powered Tag Helper renders Markdown content to HTML
<div class="course-description"><markdown content="@Model.Course.Description" /></div>
</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
37
38
39
40
41
42
43
44
45
46
47
48
49
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
39
40
41
42
43
44
45
46
47
48
49
Lesson View
The lesson view is where the core learning content is delivered. It displays the content modules for a specific lesson and provides navigation to the next lesson.
File: TheExampleApp/Pages/Courses/Lessons.cshtml
Implementation Details
This view is the most dynamic part of the course experience, designed to render a flexible structure of content modules.
- Routing: Uses a composite route
@page "{slug}/{lessonSlug}"to uniquely identify a lesson within a specific course. Due to the file's location inPages/Courses/, the complete URL pattern becomes/courses/{slug}/lessons/{lessonSlug}(e.g.,/courses/my-first-course/lessons/introduction). - Page Title: The page dynamically sets
ViewData["Title"]to the course title (Model.Course.Title). - Breadcrumbs: The page includes a breadcrumbs navigation component rendered via the
Breadcrumbspartial view in alayout-no-sidebarsection at the top of the page. - Layout: Like the course detail view, it uses the
layout-sidebarwith the Table of Contents in the sidebar for context and navigation. The Table of Contents receivesModel.Courseas the model and aViewDataDictionarycontaining the selected lesson's slug to enable proper highlighting. - Editorial Features: The view includes an
<vc:editorial-features>View Component that receivesModel.SystemProperties, allowing editors to access Contentful editorial features. - Dynamic Module Rendering: The key feature of this view is its ability to render different types of content modules. It iterates through the
Model.SelectedLesson.Modulescollection. For eachmodule, it uses the module's .NET type name to dynamically select and render a corresponding partial view.@Html.Partial(module.GetType().Name, module)
- Architectural Implication: This pattern requires a convention where for every Contentful content type that can be a "module" (e.g.,
textModule,codeBlock), a corresponding partial view must exist in theViews/Shared/directory (e.g.,TextModule.cshtml,CodeBlock.cshtml). This creates a highly extensible system for adding new content types. - Empty State: If no modules are present in the lesson, the
NoContentpartial view is rendered to provide a user-friendly message. - Navigation: A "Next Lesson" button is conditionally displayed if
Model.NextLessonSlugis not null, guiding the user through the course sequence. The button uses the localizednextLessonLabeltext.
Code Snippet: Lesson Structure with Dynamic Module Rendering
csharp
// File: TheExampleApp/Pages/Courses/Lessons.cshtml
<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
{
// Iterate over the collection of modules for the current lesson
@foreach (var module in Model.SelectedLesson.Modules)
{
// Dynamically render a partial view based on the module's content type.
// e.g., if module is of type 'CodeBlock', it will render 'CodeBlock.cshtml'
@Html.Partial(module.GetType().Name, module)
}
}
</div>
@if (!string.IsNullOrEmpty(Model.NextLessonSlug))
{
<a class="lesson__cta cta" href="/courses/@Model.Course.Slug/lessons/@Model.NextLessonSlug">@Localizer["nextLessonLabel"]</a>
}
</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
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
Course Card Component
This is a reusable partial view that provides a consistent, summarized representation of a course. It is used in the Course Catalog View and any other location where a course needs to be previewed.
File: TheExampleApp/Views/Shared/CourseCard.cshtml
Implementation Details
- Component Model: The partial view is strongly typed to the
Coursemodel (@model Course), ensuring type safety. See Content Models for its definition. - Content: It displays the course's categories, title, and short description.
- Linking: The card contains three primary links:
- Category tags link to the Course Catalog View, pre-filtered for that category.
- The course title links to the full Course Detail View.
- A "View Course" link (using the localized
viewCourseLabel) at the bottom of the card also links to the Course Detail View.
- View Component: It includes a
<vc:entry-state>View Component, which likely connects to the Contentful API to display the entry's status (e.g., Draft, Changed, Published) for editors.
Code Snippet: Course Card Structure
csharp
// File: TheExampleApp/Views/Shared/CourseCard.cshtml
@model Course
<div class="course-card">
<div class="course-card__categories">
@if (Model.Categories != null && Model.Categories.Any())
{
foreach (var category in Model.Categories)
{
<div class="course-card__category"><a class="course-card__category-link" href="/courses/categories/@category.Slug">@category.Title</a></div>
}
}
</div>
<h2 class="course-card__title">
<a href="/courses/@Model.Slug">@Model.Title</a>
<vc:entry-state sys="new[] { Model.Sys }"></vc:entry-state>
</h2>
<p class="course-card__description">
@Model.ShortDescription
</p>
<div class="course-card__link-wrapper">
<a class="course-card__link" href="/courses/@Model.Slug">@Localizer["viewCourseLabel"]</a>
</div>
</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
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
Table of Contents
This shared component provides navigation within a single course, listing all its lessons and tracking user progress.
File: TheExampleApp/Views/Shared/TableOfContents.cshtml
Implementation Details
- Usage: This partial view is included in both the Course Detail View and the Lesson View.
- Active State: It determines the currently active lesson by comparing the
lesson.Slugwith aslugvalue passed in via theViewDatadictionary. This allows the component to highlight the user's current location in the course. - Progress Tracking: The component checks for a
List<string>inViewData["VisitedLessons"]. If a lesson's ID is present in this list, avisitedCSS class is applied. The logic for populating this list (e.g., using cookies or a server-side session) resides in the backing Page Models (IndexModel.cs,LessonsModel.cs). - Structure: The list always begins with a link to the main "Course Overview" page, followed by an iterated list of all lessons associated with the
Coursemodel.
Code Snippet: Active and Visited State Logic
csharp
// File: TheExampleApp/Views/Shared/TableOfContents.cshtml
@model Course
@{
// Helper function to check if a lesson has been visited
Func<string, string> isVisited = (s) => { return (ViewData["VisitedLessons"] as List<string>).Contains(s) ? "visited" : ""; };
}
// ...
<ul class="table-of-contents__list">
<li class="table-of-contents__item">
// Link to the main course overview page
<a class="table-of-contents__link @(ViewData["slug"] == null ? "active" : "") @isVisited(Model.Sys.Id)" href="/courses/@Model.Slug">@Localizer["courseOverviewLabel"]</a>
</li>
@if (Model.Lessons != null && Model.Lessons.Any())
{
@foreach (var lesson in Model.Lessons)
{
<li class="table-of-contents__item">
// Link to an individual lesson, with dynamic classes for active and visited states
<a class="table-of-contents__link @(ViewData["slug"]?.ToString() == lesson.Slug ? "active" : "") @isVisited(lesson.Sys.Id)" href="/courses/@Model.Slug/lessons/@lesson.Slug">@lesson.Title</a>
</li>
}
}
</ul>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
Category Filtering
The category filter UI is an integral part of the course catalog, allowing users to narrow down the list of courses.
File: TheExampleApp/Pages/Courses.cshtml
Implementation Details
The filtering UI is implemented as a simple list of links within the sidebar of the course catalog page.
- Location: It resides within the
<section class="layout-sidebar__sidebar">ofCourses.cshtml. - Data Model: The
CoursesModelprovidesModel.Categories(the list of all available categories) andModel.SelectedCategory(the currently active filter, if any). - Active State: The
activeCSS class is conditionally applied to a link by comparing thecategory.Slugfrom the loop withModel.SelectedCategory?.Slug. The "All Courses" link is marked active ifModel.SelectedCategoryis null. - Routing: The links are standard anchor tags pointing to RESTful URLs (
/coursesfor all,/courses/categories/{slug}for a specific one), which are handled by the Razor Pages routing system.
Code Snippet: Category Menu
csharp
// File: TheExampleApp/Pages/Courses.cshtml
<div class="sidebar-menu">
<ul class="sidebar-menu__list">
<li class="sidebar-menu__item">
<a class="sidebar-menu__link @(Model.SelectedCategory == null ? "active" : "")" href="/courses">@Localizer["allCoursesLabel"]</a>
</li>
@if (Model.Categories != null && Model.Categories.Any())
{
@foreach (var category in Model.Categories)
{
<li class="sidebar-menu__item">
<a class="sidebar-menu__link @(Model.SelectedCategory?.Slug == category.Slug ? "active" : "")" href="/courses/categories/@category.Slug">@category.Title</a>
</li>
}
}
</ul>
</div>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Responsive Course Layouts
The views are structured with CSS classes that facilitate a responsive design, adapting from desktop to mobile screen sizes. While the specific CSS rules are not in the .cshtml files, the HTML structure is designed to support them.
layout-sidebar: This is the primary two-column layout class used for the course catalog, detail, and lesson pages. It is expected that on smaller viewports, the sidebar (layout-sidebar__sidebar) and content (layout-sidebar__content) will stack vertically.layout-no-sidebar: A simpler, single-column layout used for wrapper elements like breadcrumbs.grid-list: This class, used in the course catalog, implies a CSS Grid or Flexbox implementation. The number of columns in the grid likely decreases on smaller screens, eventually becoming a single-column list on mobile devices.
Developers should refer to the project's CSS/Sass files to understand the specific breakpoints and styling applied to these classes to ensure any modifications are consistent with the existing responsive strategy.