Appearance
Are you an LLM? You can read better optimized documentation at /architecture/overview.md for this page in Markdown format
Overview
This document provides a detailed technical overview of TheExampleApp, a server-rendered web application built on ASP.NET Core 2.1. It is intended for developers responsible for the maintenance and future development of the application. We will explore the application's architecture, key design patterns, and the flow of a typical request.
Application Architecture
TheExampleApp is a monolithic web application built using the ASP.NET Core 2.1 framework. The architecture is centered around the Razor Pages model, which favors a page-centric approach over the traditional Model-View-Controller (MVC) pattern.
Key characteristics of the architecture include:
- Server-Side Rendering (SSR): The UI is generated on the server using the Razor view engine. This simplifies the frontend, which relies on standard HTML and minimal client-side scripting, as indicated by the sparse
bower.jsonconfiguration. - Page-Centric Model: Each "page" in the application is self-contained, consisting of a
.cshtmlfile for the view and a.cshtml.csfile for the C# code-behind (thePageModel). This colocation of view and logic simplifies development for page-based features. - Unified Framework: The application leverages the
Microsoft.AspNetCore.Allmetapackage, providing a comprehensive suite of tools including the Kestrel web server, routing, dependency injection, and configuration management out of the box.
The application's entry point is the standard Program.cs, which sets up the web host.
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>() // Configuration is delegated to the Startup class
.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 request pipeline configuration occurs within the Startup.cs class, which is the heart of the application's setup.
For a deeper dive into the specifics of Razor Pages, please see the Razor Pages Architecture documentation.
Headless CMS Pattern
A core architectural decision is the decoupling of content management from the presentation layer through a headless CMS pattern. The application does not have a traditional database for its primary content; instead, it sources all content from Contentful.
Integration: Communication with the Contentful Delivery API is handled by the
contentful.aspnetcore(v3.3.6) SDK. This client is registered in the dependency injection container inStartup.cs.csharp// TheExampleApp/Startup.cs - Simplified service registration public void ConfigureServices(IServiceCollection services) { // Registers standard Contentful services from IConfiguration services.AddContentful(Configuration); // ... other services }1
2
3
4
5
6
7
8Data Fetching: Page models retrieve content at runtime by injecting the
IContentfulClient. ABasePageModelis used to provide this client to all derived page models, ensuring consistent access to the Contentful API.csharp// TheExampleApp/Models/BasePageModel.cs public class BasePageModel : PageModel { /// <summary> /// The client used to communicate with the Contentful API. /// </summary> protected readonly IContentfulClient _client; /// <summary> /// Initializes a new BasePageModel. /// </summary> /// <param name="client">The client used to communicate with the Contentful API.</param> public BasePageModel(IContentfulClient client) { _client = client; // A custom resolver is used to map Contentful content types to C# classes. _client.ContentTypeResolver = new ModulesResolver(); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Content Rendering: Content fields authored in Markdown within Contentful are parsed and rendered to HTML using the Markdig library (v0.15.4).
Dynamic Configuration: The application uses a custom
IContentfulOptionsManagerimplementation (ContentfulOptionsManager) instead of the standard Options pattern. This allows Contentful connection settings to be loaded from memory and changed within the application at runtime without requiring a restart.Staging Environment Support: For staging deployments, the application can use a custom
StagingMessageHandlerthat redirects Contentful API requests to a staging host. This is configured via theStagingHostenvironment variable when the hosting environment is set to "Staging" (determined byIHostingEnvironment.IsStaging()).Gotcha: Custom Client Registration: Note the custom registration for
IContentfulClientinStartup.cs. This is done to inject a customHttpClientfor staging environments and to add a custom user-agent string for analytics. Developers should be aware of this override if they need to modify client behavior.csharp// TheExampleApp/Startup.cs services.AddTransient<IContentfulClient, ContentfulClient>((ip) => { var client = ip.GetService<HttpClient>(); var options = ip.GetService<IContentfulOptionsManager>().Options; var contentfulClient = new ContentfulClient(client, options); var version = typeof(Startup).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>() .InformationalVersion; contentfulClient.Application = $"app the-example-app.csharp/{version}; {contentfulClient.Application}"; return contentfulClient; });1
2
3
4
5
6
7
8
9
10
For more details on this integration, refer to the Contentful Integration guide.
Project Structure
The solution (TheExampleApp.sln) is organized into four projects, following standard .NET conventions for separating application code from tests.
| Project | Purpose |
|---|---|
TheExampleApp | The main ASP.NET Core web application project. |
TheExampleApp.Tests | Unit tests for the application, using xUnit and Moq. |
TheExampleApp.IntegrationTests | Integration tests for the application. |
TheExampleApp.E2eTests | End-to-end tests for the application. |
Within the main TheExampleApp project, the directory structure is organized as follows:
/Pages: Contains all Razor Pages. Each page consists of a.cshtmlview file and a.cshtml.csPageModelfile. This directory includes subdirectories for logical grouping (e.g.,/Pages/Courses)./Models: Contains Plain Old C# Objects (POCOs) that represent the data structures of the application, primarily mapping to Contentful content types.BasePageModel.csis a key file here./Configuration: Holds classes related to application configuration, such asContentfulOptionsManagerand custom localization providers./wwwroot: The web root for static assets like images, stylesheets, and client-side scripts. The locale JSON files (en-US.json,de-DE.json) are also located here and embedded as assembly resources during the build process.Startup.cs: The central configuration file for services and the HTTP request pipeline.Program.cs: The application's main entry point.
Key Design Patterns
The application consistently employs several fundamental design patterns common in modern ASP.NET Core development.
Dependency Injection (DI): DI is used extensively to achieve loose coupling and improve testability. Services are registered in the
ConfigureServicesmethod ofStartup.csand injected into constructors throughout the application. Key registered services include:IContentfulClient- for accessing the Contentful APIIContentfulOptionsManager- for managing Contentful configurationIViewLocalizer- customJsonViewLocalizerfor localization using embedded JSON filesIVisitedLessonsManager- for tracking user progress through lessonsIBreadcrumbsManager- for managing navigation breadcrumbsIHttpContextAccessor- for accessing HTTP context throughout the application See the Dependency Injection documentation for more information.
Options Pattern: The application uses a custom implementation of the Options pattern (
IContentfulOptionsManager) to manage Contentful connection settings. This allows for configuration to be managed and potentially reloaded at runtime without requiring an application restart.Middleware: The HTTP request pipeline in
Startup.Configureis composed of a series of middleware components. Each piece of middleware inspects or modifies the HTTP context before passing it to the next component. Key middleware includesUseStaticFiles,UseSession,UseRequestLocalization, andUseMvc. The application also includes custom middleware for breadcrumb navigation (UseBreadcrumbs) and deeplink handling (UseDeeplinks).Repository-like Pattern for Content: While not a formal Repository pattern, the
IContentfulClientacts as an abstraction layer for data access. Page models interact with this client to fetch data, isolating them from the specifics of the Contentful API calls.Session State: The application uses session state with a custom
IdleTimeoutof 2 days (configured inStartup.ConfigureServices). This extended timeout is specific to this application's requirements; typical applications should use shorter timeouts (the default is 20 minutes) for better security.
Request/Response Flow
Understanding the request pipeline is crucial for debugging and adding new features. A typical request to a page follows this sequence:
- Request Arrival: An HTTP request is received by the Kestrel server.
- Middleware Execution: The request passes through the pipeline defined in
Startup.Configure.- Error Handling:
UseDeveloperExceptionPage(in Development) orUseExceptionHandler(in Production) is configured to catch exceptions. - HTTPS Redirect (Production): In non-development environments, requests with the
X-Forwarded-Proto: httpheader are redirected to HTTPS with a 301 status code. - URL Rewriting:
UseRewriterapplies URL rewrite and redirect rules (e.g., redirecting.../lessonsto.../). - Static Files:
UseStaticFilesserves static assets like CSS or images directly from/wwwroot, short-circuiting the rest of the pipeline if a match is found. - Session:
UseSessionenables session state, which is used by the localization logic and other features. - Localization:
UseRequestLocalizationdetermines the correct culture for the request. This application uses a sophisticatedCustomRequestCultureProviderthat checks the query string, session state, and Contentful's locale fallback chain to determine the appropriate language. If a locale is supported in Contentful but not by the application, it walks through the fallback chain to find a supported locale. - Status Code Pages:
UseStatusCodePagesWithReExecutere-executes the pipeline at/Errorfor non-success status codes. - Custom Middleware:
UseBreadcrumbsandUseDeeplinksprovide custom functionality for navigation breadcrumbs and deep linking. - Routing:
UseMvc(which includes Razor Pages routing) matches the request URL to a specific Razor Page handler. Custom routes are defined inStartup.csto create friendly URLs for courses and lessons.
- Error Handling:
- Page Activation: The framework activates the matched Razor Page.
- Dependency Injection: The
PageModelfor the page is instantiated, and its dependencies (e.g.,IContentfulClient,IBreadcrumbsManager,IVisitedLessonsManager) are injected via its constructor. - Handler Execution: The appropriate handler method (e.g.,
OnGetAsync) is invoked. This method contains the core logic, such as calling the Contentful client to fetch data. - Model Binding & Rendering: The data fetched is assigned to properties on the
PageModel. The Razor view engine then executes the corresponding.cshtmlfile, using the model's properties to render the final HTML. - Response: The generated HTML is sent back to the client as the HTTP response.
Architectural Diagrams
To visualize these concepts, here are two diagrams representing the application's structure and request flow.
High-Level Component Diagram
This diagram shows the main components and their relationships. The browser interacts with the ASP.NET Core application, which in turn fetches content from the external Contentful CMS.
Request Flow Sequence Diagram
This diagram illustrates the sequence of events during a typical page request.