Appearance
Are you an LLM? You can read better optimized documentation at /models/layout_modules.md for this page in Markdown format
Layout modules
Overview
This document details the modular architecture used for composing flexible page layouts within the application. The system leverages a "slice" or "module" based approach, where pages are constructed by assembling a sequence of different content blocks. This design is powered by the Contentful headless CMS, allowing content editors to dynamically create and reorder page content without requiring code changes or deployments.
Each layout module corresponds to a specific Contentful Content Type and is represented in the C# backend by a dedicated model class. These models are polymorphically deserialized from the Contentful API response into a list of strongly-typed objects, which are then rendered by the view layer.
The primary goals of this architecture are:
- Flexibility: Enable non-technical users to build diverse page layouts.
- Reusability: Create a library of standard content blocks that can be used across multiple pages.
- Maintainability: Decouple page structure from application code, making both easier to manage.
IModule interface
The foundation of the modular system is a set of marker interfaces that establish a common contract for all module types. The core interface is IModule, with more specific interfaces ILayoutModule and ILessonModule inheriting from it. This page focuses on ILayoutModule.
These interfaces ensure that all module classes include the Sys property, which is essential for the Contentful SDK to manage entity metadata.
Source: TheExampleApp/Models/IModule.cs
csharp
using Contentful.Core.Models;
namespace TheExampleApp.Models
{
/// <summary>
/// Interface to mark which classes can be used as modules.
/// </summary>
public interface IModule
{
SystemProperties Sys { get; set; }
}
/// <summary>
/// Interface to mark lesson modules.
/// </summary>
public interface ILessonModule : IModule
{
}
/// <summary>
/// Interface to mark layout modules.
/// </summary>
public interface ILayoutModule : IModule
{
}
}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
IModule: The base interface for any content block that can be part of a larger collection. It mandates theSystemProperties Sysproperty, provided by the Contentful SDK, which contains metadata like the entry's ID, version, and timestamps.ILayoutModule: The specific marker interface for modules used in general page layouts. All classes described in this document implement this interface.ILessonModule: A distinct marker interface for modules used within the context of a lesson. For more details, see the Lesson Modules documentation.
ModulesResolver
The ModulesResolver is a critical component that enables the polymorphic deserialization of module collections. When the application fetches a page from Contentful, the layout is typically represented as a "links" field containing an array of entries. The Contentful SDK needs to know which C# class to instantiate for each entry in this array.
The ModulesResolver implements the IContentTypeResolver interface from the Contentful SDK. Its Resolve method maps a Contentful Content Type ID (a string) to a C# Type. This resolver is registered with the ContentfulClient at application startup.
Source: TheExampleApp/Configuration/ModulesResolver.cs
csharp
using Contentful.Core.Configuration;
using System;
using System.Collections.Generic;
using TheExampleApp.Models;
namespace TheExampleApp.Configuration
{
/// <summary>
/// Resolves a strong type from a content type id. Instructing the serialization engine how to deserialize items in a collection.
/// </summary>
public class ModulesResolver : IContentTypeResolver
{
private Dictionary<string, Type> _types = new Dictionary<string, Type>()
{
// Mappings for Layout Modules
{ "layoutCopy", typeof(LayoutCopy) },
{ "layoutHeroImage", typeof(LayoutHeroImage) },
{ "layoutHighlightedCourse", typeof(LayoutHighlightedCourse) },
// Mappings for Lesson Modules
{ "lessonCodeSnippets", typeof(LessonCodeSnippets) },
{ "lessonCopy", typeof(LessonCopy) },
{ "lessonImage", typeof(LessonImage) },
};
/// <summary>
/// Method to get a type based on the specified content type id.
/// </summary>
/// <param name="contentTypeId">The content type id to resolve to a type.</param>
/// <returns>The type for the content type id or null if none is found.</returns>
public Type Resolve(string contentTypeId)
{
return _types.TryGetValue(contentTypeId, out var type) ? type : null;
}
}
}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
The _types dictionary is the core of this class, acting as a registry. When adding a new module, it is essential to add its mapping here. See the Extensibility section for more details.
LayoutCopy
This is a versatile module for displaying rich text content. It typically includes a headline, a body of text, and an optional call-to-action (CTA) button.
Source: TheExampleApp/Models/Layout - Copy.cs
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Contentful.Core.Models;
namespace TheExampleApp.Models
{
/// <summary>
/// Represents a copy module used for a layout.
/// </summary>
public class LayoutCopy : ILayoutModule
{
/// <summary>
/// The system defined meta data properties.
/// </summary>
public SystemProperties Sys { get; set; }
/// <summary>
/// The title of the module.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The headline of the module.
/// </summary>
public string Headline { get; set; }
/// <summary>
/// The body copy of the module.
/// </summary>
public string Copy { get; set; }
/// <summary>
/// The call to action title.
/// </summary>
public string CtaTitle { get; set; }
/// <summary>
/// The call to action link.
/// </summary>
public string CtaLink { get; set; }
/// <summary>
/// The visual style of this module.
/// </summary>
public string VisualStyle { get; set; }
}
}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
50
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
50
Properties:
| Property | Type | Description |
|---|---|---|
Title | string | Internal title for identifying the module in the Contentful UI. |
Headline | string | The main headline displayed to the user. |
Copy | string | The main body of text. Given the inclusion of the Markdig library, this field is expected to contain Markdown, which must be parsed before rendering. |
CtaTitle | string | The text for the call-to-action button (e.g., "Learn More"). |
CtaLink | string | The URL for the CTA button. Can be an internal or external link. |
VisualStyle | string | A string identifier (e.g., image-left, dark-theme) used by the view to apply different CSS classes and styles. This allows content editors to vary the presentation. |
LayoutHeroImage
This module is designed to be a prominent "hero" banner, typically placed at the top of a page. It features a large background image and overlayed text.
Source: TheExampleApp/Models/Layout - Hero Image.cs
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Contentful.Core.Models;
namespace TheExampleApp.Models
{
/// <summary>
/// Represents a hero image module for a layout.
/// </summary>
public class LayoutHeroImage : ILayoutModule
{
/// <summary>
/// The system defined meta data properties.
/// </summary>
public SystemProperties Sys { get; set; }
/// <summary>
/// The title of the module.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The headline of the module.
/// </summary>
public string Headline { get; set; }
/// <summary>
/// The background image of the module.
/// </summary>
public Asset BackgroundImage { get; set; }
}
}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
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
Properties:
| Property | Type | Description |
|---|---|---|
Title | string | Internal title for identifying the module in the Contentful UI. |
Headline | string | The primary text displayed over the background image. |
BackgroundImage | Asset | A reference to an image asset in Contentful. The Asset object contains the URL, dimensions, and other metadata for the image. |
LayoutHighlightedCourse
This module serves to feature a specific course, linking directly to it. It demonstrates how modules can link to other complex content types within Contentful.
Source: TheExampleApp/Models/Layout - Highlighted Course.cs
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Contentful.Core.Models;
namespace TheExampleApp.Models
{
/// <summary>
/// Represents a highlighted course module for a layout.
/// </summary>
public class LayoutHighlightedCourse : ILayoutModule
{
/// <summary>
/// The system defined meta data properties.
/// </summary>
public SystemProperties Sys { get; set; }
/// <summary>
/// The title of the module.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The course that should be highlighted.
/// </summary>
public Course Course { get; set; }
}
}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
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
Properties:
| Property | Type | Description |
|---|---|---|
Title | string | Internal title for identifying the module in the Contentful UI. |
Course | Course | A linked entry reference to a Course content type. The SDK will resolve this into a full Course object, providing access to all of its properties (e.g., title, slug, description). See Content Models for the Course model definition. |
Module rendering
While the specific view files are not detailed here, rendering the list of modules follows a standard and predictable pattern in ASP.NET Core MVC. The main page's view model will contain a collection of ILayoutModule objects (e.g., List<ILayoutModule> Modules). The Razor view then iterates over this collection and uses a type-checking mechanism to render the appropriate partial view for each module.
This approach keeps the main view clean and delegates the rendering logic for each module to its own dedicated partial view. For more on shared views, see the Shared Views documentation.
Example Rendering Logic in a Razor View (e.g., Views/Pages/Index.cshtml):
csharp
// Assuming the model for this view has a property:
// public List<ILayoutModule> LayoutModules { get; set; }
@foreach (var module in Model.LayoutModules)
{
// Use a series of if/else if blocks to check the concrete type of the module.
if (module is LayoutCopy copyModule)
{
// Render the partial view for the LayoutCopy module, passing the typed model.
<partial name="_LayoutCopy" model="copyModule" />
}
else if (module is LayoutHeroImage heroModule)
{
<partial name="_LayoutHeroImage" model="heroModule" />
}
else if (module is LayoutHighlightedCourse highlightedCourseModule)
{
<partial name="_LayoutHighlightedCourse" model="highlightedCourseModule" />
}
// Add other module types here...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Each partial view (e.g., Views/Shared/Partials/_LayoutCopy.cshtml) would then be strongly-typed to its corresponding model and contain the specific HTML markup.
Extensibility
Adding a new layout module type is a routine task that involves changes in the data model, configuration, and view layer. Follow these steps to add a new module (e.g., a "Video Embed" module).
Define in Contentful: In the Contentful web app, create a new Content Type.
- Name:
Video Embed - Content Type ID:
layoutVideoEmbed - Fields:
title(Short Text),videoUrl(Short Text, with URL validation),caption(Short Text).
- Name:
Create C# Model: In the
TheExampleApp/Modelsdirectory, create a new classLayoutVideoEmbed.cs. It must implementILayoutModule.csharp// TheExampleApp/Models/LayoutVideoEmbed.cs using Contentful.Core.Models; namespace TheExampleApp.Models { public class LayoutVideoEmbed : ILayoutModule { public SystemProperties Sys { get; set; } public string Title { get; set; } // Corresponds to 'title' field public string VideoUrl { get; set; } // Corresponds to 'videoUrl' field public string Caption { get; set; } // Corresponds to 'caption' field } }1
2
3
4
5
6
7
8
9
10
11
12
13Update ModulesResolver: Register the new type in
TheExampleApp/Configuration/ModulesResolver.cs. This is a critical step for deserialization.csharp// In TheExampleApp/Configuration/ModulesResolver.cs private Dictionary<string, Type> _types = new Dictionary<string, Type>() { { "layoutCopy", typeof(LayoutCopy) }, { "layoutHeroImage", typeof(LayoutHeroImage) }, { "layoutHighlightedCourse", typeof(LayoutHighlightedCourse) }, { "layoutVideoEmbed", typeof(LayoutVideoEmbed) }, // Add this line // ... other types };1
2
3
4
5
6
7
8
9Create Partial View: Create a new Razor partial view, for example
Views/Shared/Partials/_LayoutVideoEmbed.cshtml.csharp@model TheExampleApp.Models.LayoutVideoEmbed <div class="video-embed-module"> <iframe src="@Model.VideoUrl" frameborder="0" allowfullscreen></iframe> @if (!string.IsNullOrEmpty(Model.Caption)) { <p class="caption">@Model.Caption</p> } </div>1
2
3
4
5
6
7
8
9Update Rendering Logic: Add the new type check to the main rendering loop in the page view.
csharp// In the main page view's @foreach loop // ... else if (module is LayoutHighlightedCourse highlightedCourseModule) { <partial name="_LayoutHighlightedCourse" model="highlightedCourseModule" /> } else if (module is LayoutVideoEmbed videoModule) // Add this block { <partial name="_LayoutVideoEmbed" model="videoModule" /> }1
2
3
4
5
6
7
8
9
10