Appearance
Are you an LLM? You can read better optimized documentation at /architecture/application_overview.md for this page in Markdown format
Application Overview
Note: This application is no longer officially maintained as of January 2023. It remains available as a reference implementation and educational resource.
This document provides a comprehensive technical overview of the TheExampleApp web application. TheExampleApp is an example application designed to teach the basics of working with Contentful, including how to consume content from the Contentful Delivery and Preview APIs, model content, and edit content through the Contentful web app. The application demonstrates how decoupling content from its presentation enables greater flexibility and facilitates shipping higher quality software more quickly.
This document is intended for developers responsible for the maintenance and future development of the system. It covers the system's architecture, the technology stack, and the key architectural decisions that have shaped its design.
For a quick start on setting up the development environment, please refer to the Introduction.
System Architecture
TheExampleApp is a server-side rendered (SSR) web application built on the .NET Core platform. Its primary function is to display content managed through a headless Content Management System (CMS). The architecture is designed to be simple, robust, and content-focused, deliberately avoiding the complexity of a Single-Page Application (SPA) framework.
The core request-response lifecycle is as follows:
- A user request hits the Kestrel web server.
- The ASP.NET Core middleware pipeline is executed. This includes:
- URL rewriting (including HTTPS redirect in production environments)
- Static file serving
- Session management
- Request localization for internationalization (i18n)
- Custom breadcrumbs and deeplinks middleware
- Routing and error handling
- The request is routed to a specific Razor Page.
- The Razor Page's model fetches the required content from the Contentful CMS via the
contentful.aspnetcoreSDK. - The fetched content, which may include Markdown, is processed (e.g., Markdown is converted to HTML using the Markdig library).
- The Razor engine renders the final HTML page using the processed content.
- The complete HTML page is sent back to the client's browser.
This SSR approach ensures fast initial page loads and strong SEO performance, as the content is fully rendered on the server before being delivered to the client.
The application's entry point is standard for an ASP.NET Core 2.1 application, defined in TheExampleApp/Program.cs:
csharp
// TheExampleApp/Program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
All service registration and middleware configuration is centralized in TheExampleApp/Startup.cs. For a detailed breakdown of the project's file and directory layout, see the Project Structure documentation.
Solution Structure
The application is organized as a Visual Studio solution containing four distinct projects:
- TheExampleApp - The main web application project
- TheExampleApp.Tests - Unit tests for isolated component testing
- TheExampleApp.IntegrationTests - Integration tests for testing component interactions
- TheExampleApp.E2eTests - End-to-end tests for full application workflow testing
This multi-project structure reflects a comprehensive testing strategy with three dedicated test projects covering different testing levels.
Technology Stack
The application is built using a curated set of technologies centered around the Microsoft .NET ecosystem and a headless content architecture.
Backend
Language: C#
Framework: ASP.NET Core 2.1. The project utilizes the
Microsoft.AspNetCore.Allmetapackage, which provides a comprehensive set of functionalities including the MVC framework, Kestrel web server, configuration, and dependency injection.xml<!-- TheExampleApp/TheExampleApp.csproj --> <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.5" /> ... </ItemGroup> </Project>1
2
3
4
5
6
7
8
9
10
Frontend
- Templating Engine: Razor Pages. The UI is rendered on the server using Razor Pages, the standard page-focused model for ASP.NET Core. This choice aligns with the SSR architecture. For more details on how Razor Pages are used, see the Razor Pages documentation.
- Client-Side Dependencies: The application does not use a major client-side JavaScript framework (e.g., React, Vue). Frontend package management was handled by Bower, but the
bower.jsonfile is empty, confirming the minimal client-side footprint.
Content & Data
- Content Management System: Contentful. All application content is sourced from Contentful, a headless CMS. The integration is managed by the
contentful.aspnetcoreSDK. - Markdown Processing: Markdig. The application uses the Markdig library to parse and render Markdown into HTML. This indicates that content fields within Contentful are expected to be in Markdown format.
- Database: The application does not use a traditional relational or NoSQL database. Contentful serves as the sole persistence layer for content.
Key Library Versions
| Technology | Version | Role |
|---|---|---|
| .NET Core | netcoreapp2.1 | Runtime Framework |
| ASP.NET Core (Metapackage) | 2.1.5 | Web Framework |
| Contentful ASP.NET Core SDK | 3.3.6 | CMS Integration |
| Markdig | 0.15.4 | Markdown to HTML Rendering |
| Visual Studio Code Generation | 2.1.5 | Scaffolding Tooling |
Key Architectural Decisions
The following sections detail the rationale behind the most significant architectural choices made during the application's development.
Server-Side Rendering vs. SPA
The decision to use a traditional Server-Side Rendering (SSR) model with Razor Pages instead of a client-side Single-Page Application (SPA) was intentional.
Rationale: For a content-heavy site, SSR offers superior SEO capabilities and faster perceived performance on initial load, as the browser receives a fully-formed HTML document. This approach simplifies the frontend development stack, reducing complexity and dependencies by eliminating the need for a JavaScript framework, build tools, and client-side routing.
Implementation: The use of Razor Pages is configured in
Startup.cs, where custom routes are also defined to create user-friendly URLs.csharp// TheExampleApp/Startup.cs public void ConfigureServices(IServiceCollection services) { // ... other services services.AddMvc().AddRazorPagesOptions( options => { options.Conventions.AddPageRoute("/Courses", "Courses/Categories/{category?}"); options.Conventions.AddPageRoute("/Courses/Index", "Courses/{slug}/lessons"); options.Conventions.AddPageRoute("/Courses/Lessons", "Courses/{slug}/lessons/{lessonSlug}"); }); // ... }1
2
3
4
5
6
7
8
9
10
11
12
Headless CMS Approach
The application is architected around a headless CMS (Contentful) rather than a monolithic CMS or a traditional database.
Rationale: This decouples content management from the presentation layer (the .NET application). It empowers content editors to work independently in the Contentful web app while developers focus on the application code. This separation makes the system more flexible and easier to maintain or re-platform in the future.
Implementation: The Contentful SDK is registered as a service in
Startup.cs. The application uses the standard .NET Core configuration pattern to read API keys and the Space ID fromappsettings.json.csharp// TheExampleApp/Startup.cs - Registering Contentful services public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddContentful(Configuration); // ... }1
2
3
4
5
6
7The application implements a custom
IContentfulOptionsManagerto allow Contentful configuration options to be loaded from memory when they are changed within the application, rather than solely relying on static configuration files. Additionally, a customIContentfulClientis registered to inject application version information into the user-agent string for API requests, which is useful for logging and debugging. For a deep dive into the specifics of this integration, please see the Contentful Integration documentation.
Configuration Management
The application leverages the standard ASP.NET Core configuration system for managing settings.
Rationale: This provides a flexible and robust way to handle application settings across different environments (Development, Staging, Production). Configuration can be sourced from files (
appsettings.json), environment variables, or other providers. This is critical for deploying to platforms like Azure or Heroku, where credentials are often injected as environment variables.Implementation: The
IConfigurationinstance is injected into theStartupconstructor and used to configure services like Contentful. TheREADME.mdprovides clear instructions for setting up theContentfulOptionssection inappsettings.json.json// appsettings.json structure { "ContentfulOptions": { "DeliveryApiKey": "<DELIVERY_ACCESS_TOKEN>", "PreviewApiKey": "<PREVIEW_ACCESS_TOKEN>", "SpaceId": "<SPACE_ID>", "UsePreviewApi": false } }1
2
3
4
5
6
7
8
9The code also includes a specific handler for a staging environment, which demonstrates the power of environment-based configuration. The
StagingMessageHandlerintercepts outgoing requests to Contentful and redirects them to a staging host defined by an environment variable.csharp// TheExampleApp/Startup.cs - Staging environment specific configuration if (CurrentEnvironment.IsStaging()) { var host = Environment.GetEnvironmentVariable("StagingHost"); if (!string.IsNullOrEmpty(host)) { services.AddSingleton((ip) => { var stagingHandler = new StagingMessageHandler { StagingHost = host }; return new HttpClient(stagingHandler); }); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Internationalization (i18n)
The application is designed to support multiple languages, with built-in support for English (en-US) and German (de-DE).
Rationale: To serve a global audience, the application needs to display content and UI text in the user's preferred language. The i18n system is designed to be flexible, handling locales defined both within the application and within Contentful.
Implementation: The implementation is sophisticated and represents a critical area for developers to understand.
- Supported Cultures: A static list in
Startup.csdefines the languages the application's static UI text has been translated into. - Locale Detection: A
CustomRequestCultureProvideris used to determine the active locale. It prioritizes thelocalequery string parameter (e.g.,?locale=de-DE). - Contentful Fallback Logic: The system checks if the requested locale is supported by Contentful. If the locale is supported by Contentful but not by the application's static UI resources, it traverses Contentful's fallback chain to find a locale that the application does support for its UI. This ensures that the user sees content in their desired language, while the UI falls back gracefully to a supported language.
- Session Storage: The chosen locale is stored in the user's session to persist across requests.
- Static Text: Static UI strings are loaded from embedded JSON files (e.g.,
en-US.json,de-DE.json) using a customJsonViewLocalizer.
csharp// TheExampleApp/Startup.cs - Defining supported cultures public static List<CultureInfo> SupportedCultures = new List<CultureInfo> { //When adding supported locales make sure to also add a static translation files for the locale under /wwwroot/locales new CultureInfo("en-US"), new CultureInfo("de-DE"), }; // TheExampleApp/Startup.cs - Configuring the request localization pipeline app.UseRequestLocalization(new RequestLocalizationOptions { RequestCultureProviders = new List<IRequestCultureProvider> { // Custom provider to check query string, validate against Contentful, and manage fallbacks new CustomRequestCultureProvider(async (s) => { /* ... */ }), new QueryStringRequestCultureProvider() { QueryStringKey = LOCALE_KEY }, // Custom provider to read the culture from the session new CustomRequestCultureProvider(async (s) => { /* ... */ }) }, DefaultRequestCulture = new RequestCulture("en-US"), SupportedCultures = SupportedCultures, SupportedUICultures = SupportedCultures });1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Note: Developers modifying or adding languages must update the
SupportedCultureslist inStartup.csand provide a corresponding[locale].jsonfile in thewwwroot/localesdirectory.- Supported Cultures: A static list in
Session Management
The application uses ASP.NET Core's session middleware to maintain state across requests, primarily for storing the user's selected locale.
Rationale: Session storage is essential for persisting the user's language preference and visited lessons without requiring authentication or a database.
Implementation: Session middleware is configured in
Startup.cswith an extended idle timeout of 2 days, which is specific to this application's requirements. In typical applications, the default 20-minute timeout is more appropriate.csharp// TheExampleApp/Startup.cs - Session configuration services.AddSession(options => { // IdleTimeout is set to a high value to confirm 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); });1
2
3
4
5
6
Middleware Pipeline
The application configures a comprehensive middleware pipeline in the Configure method of Startup.cs. The order of middleware is critical to proper application behavior:
- Exception Handling: Developer exception page in development; custom error handler in production
- URL Rewriting:
- HTTPS redirect in production (based on
X-Forwarded-Protoheader) - Legacy URL redirects (e.g.,
/courses/{slug}/lessons→/courses/{slug})
- HTTPS redirect in production (based on
- Static Files: Serves files from
wwwroot - Session: Enables session state
- Request Localization: Determines and sets the user's culture
- Status Code Pages: Custom error page handling
- Breadcrumbs: Custom middleware for navigation breadcrumbs
- Deeplinks: Custom middleware for handling deep links
- MVC: Routes requests to controllers and Razor Pages