Appearance
Are you an LLM? You can read better optimized documentation at /configuration/middleware.md for this page in Markdown format
Middleware
This document provides a detailed technical overview of the ASP.NET Core middleware pipeline for TheExampleApp. It covers the configuration, purpose, and implementation of each component in the request processing chain. This information is essential for developers maintaining or extending the application's request handling logic.
Overview
In ASP.NET Core, middleware components are chained together to form a request pipeline. Each component has the opportunity to inspect, modify, or pass along an incoming HTTP request, and to perform actions on the outgoing response. The application's middleware pipeline is defined and ordered within the Configure method of the Startup.cs class.
The pipeline in this application is responsible for several key cross-cutting concerns:
- Environment-specific error handling
- HTTPS enforcement behind a reverse proxy
- URL rewriting for canonical routes
- Serving static assets like CSS and images
- Session state management
- Internationalization (i18n) through dynamic request localization
- Custom application features such as breadcrumb generation and Contentful deeplinking
A correct understanding of this pipeline is crucial for debugging request-related issues and for correctly integrating new features. For a higher-level view of the application's structure, please refer to the Architecture Overview.
Middleware pipeline
The order in which middleware is registered in Startup.Configure is critical, as it defines the sequence in which requests are processed. The following list details the configured pipeline for this application:
- Exception Handling (
UseDeveloperExceptionPage/UseExceptionHandler): The first component, handling errors differently in development versus production. In development,UseBrowserLink()is also registered for Visual Studio integration. - HTTPS Redirection (
UseRewriter): A custom rewrite rule enforces HTTPS by inspecting proxy headers (production only). - URL Rewriting (
UseRewriter): Redirects non-canonical URLs to their correct form. - Static Files (
UseStaticFiles): Serves static assets directly fromwwwroot, short-circuiting the pipeline for these requests. - Session (
UseSession): Enables session state, making it available to subsequent middleware. - Request Localization (
UseRequestLocalization): Determines the appropriate culture for the request based on query strings and session state. - Status Code Pages (
UseStatusCodePagesWithReExecute): Handles non-success HTTP status codes (e.g., 404) by re-executing the request to a friendly error page. - Breadcrumbs (
UseBreadcrumbs): A custom middleware to generate breadcrumb navigation data from the request path. - Deeplinks (
UseDeeplinks): A custom middleware that allows overriding Contentful settings via query parameters for preview and testing. - MVC (
UseMvc): The final component that routes the request to the appropriate Razor Page or controller.
HTTPS redirection
In non-development environments, the application enforces HTTPS. Instead of using the standard app.UseHttpsRedirection(), a custom rewrite rule is implemented. This approach is specifically designed for scenarios where the application runs behind a reverse proxy (e.g., a load balancer or container orchestrator) that terminates SSL.
The rule inspects the X-Forwarded-Proto header. If this header is present and its value is http, the middleware issues a 301 Moved Permanently redirect to the equivalent https URL.
csharp
// In Startup.Configure, inside the 'else' block for non-development environments
options.Add((c) => {
var request = c.HttpContext.Request;
// Check if the request was forwarded and the original protocol was HTTP
if(request.Headers.ContainsKey("X-Forwarded-Proto") && request.Headers["X-Forwarded-Proto"] == "http")
{
var response = c.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
c.Result = RuleResult.EndResponse; // Short-circuit the request
response.Headers[HeaderNames.Location] = "https://" + request.Host + request.Path + request.QueryString;
}
});
app.UseRewriter(options);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
This explicit check is more reliable than the default middleware in environments where the Kestrel server itself is not aware it's being accessed via HTTPS.
Static files middleware
The app.UseStaticFiles() middleware is configured to serve files directly from the application's web root directory (wwwroot). This is used for all client-side assets, including:
- CSS stylesheets
- JavaScript files
- Images and fonts
- Static locale JSON files (
/wwwroot/locales)
Requests for files within wwwroot are handled efficiently by this middleware and do not proceed further down the pipeline to session or MVC logic.
Session middleware
Session state is enabled via app.UseSession(). This middleware must be registered before any other components that need to access session data, such as UseRequestLocalization and our custom UseDeeplinks.
Configuration for the session is performed in Startup.ConfigureServices:
csharp
// In Startup.ConfigureServices
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
2
3
4
5
6
Important: The IdleTimeout has been set to 2 days, which is significantly longer than the default of 20 minutes. This was a specific project requirement. When modifying session-related behavior, consider if this long timeout is still appropriate.
The session is used to persist:
- Contentful API settings when overridden via the Deeplinker middleware.
- The user's selected locale and fallback locale.
- The state of "editorial features".
- Validation errors from the Deeplinker.
Request localization
The application's internationalization strategy is implemented via a sophisticated UseRequestLocalization configuration. It uses a chain of custom providers to determine the request's culture. For more details on the i18n feature itself, see the Internationalization documentation.
The culture is determined by the following providers, in order:
Custom
localeQuery String Provider:- Checks for a
localequery string parameter (e.g.,?locale=de-DE). - Validates the requested locale against the list of available locales fetched from the Contentful space.
- If the locale is valid in Contentful but not directly supported by the app's static resources (
en-US,de-DE), it traverses Contentful's fallback chain to find a supported culture. - It stores the original requested locale in
Session["locale"]and the determined fallback (if any) inSession["fallback-locale"]. This allows the app to render UI chrome in a supported language (e.g.,en-US) while fetching content from Contentful in an unsupported language (e.g.,es-ES) that falls back toen-US. - This provider ensures that content and UI localization are robustly aligned with the CMS configuration.
- Checks for a
Standard
QueryStringRequestCultureProvider:- A built-in provider that also looks for the
localequery string. It acts as a fallback but is largely superseded by the custom provider above.
- A built-in provider that also looks for the
Custom Session Provider:
- Reads the culture from the session keys (
locale,fallback-locale) set by the first provider. - This ensures the selected culture persists across subsequent requests within the same user session. It prioritizes the
fallback-localefor UI consistency if one is set.
- Reads the culture from the session keys (
If no culture is determined by these providers, the application defaults to en-US.
Custom middleware
The application includes two custom middleware components to handle application-specific logic.
Breadcrumbs Middleware
Registered via app.UseBreadcrumbs(), this middleware is responsible for generating breadcrumb navigation data for the current request. See the Breadcrumb Navigation feature documentation for more information.
Implementation (Breadcrumbs.cs):
- It runs on every request.
- It splits the request
Pathinto segments. - For each segment, it creates a
Breadcrumbobject containing aLabelandPath. - It attempts to translate the label using
IViewLocalizerby looking for a localization key (e.g.,coursesLabel). - The resulting
List<Breadcrumb>is stored inHttpContext.Items["breadcrumbs"], making it available only for the duration of the current request.
csharp
// In Breadcrumbs.cs
public async Task Invoke(HttpContext context)
{
var path = context.Request.Path;
var parts = path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries);
var items = new List<Breadcrumb>();
items.Add(new Breadcrumb { Label = _localizer["homeLabel"].Value, Path = "/" });
var translations = _localizer.GetAllStrings(false);
foreach (var part in parts)
{
var label = part.Replace("-", " ");
if(translations.Any(c => c.Name == $"{LowerCaseFirstChar(label)}Label"))
{
label = _localizer[$"{LowerCaseFirstChar(label)}Label"].Value;
}
items.Add(new Breadcrumb { Label = label, Path = $"/{string.Join('/', parts.Take(Array.IndexOf(parts, part) + 1))}" });
}
context.Items["breadcrumbs"] = items;
await _next(context);
}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
The BreadcrumbsManager service can then be injected into Razor Pages to modify these breadcrumbs (e.g., replacing a URL slug with a human-readable title fetched from Contentful).
Deeplinks Middleware
Registered via app.UseDeeplinks(), this component provides a powerful mechanism for developers and content editors to preview content from different Contentful environments or with different feature flags enabled.
Implementation (Deeplinker.cs): This middleware inspects the query string for specific parameters on every request:
- Contentful Environment Override: If
space_id,preview_token, anddelivery_tokenare all present, it constructs a newContentfulOptionsobject.- These options are validated against data annotation attributes. On validation failure, errors are stored in the session as
SettingsErrors(serialized as JSON), along with the attempted options inSettingsErrorsOptions. The user is then redirected to the/settingspage with cache-control headers (no-cache, no-storeandPragma: no-cache) to prevent caching of the error state. - On success, the new
ContentfulOptionsobject is serialized and stored in the session under the keyContentfulOptions, overriding the application's default Contentful configuration for that user's session.
- These options are validated against data annotation attributes. On validation failure, errors are stored in the session as
- API Switching: If only the
apiquery parameter is present (without the full set of credentials), the middleware will use the existing configured credentials but toggle the API type. A value of?api=cpawill setUsePreviewApitotrue, switching to the Preview API instead of the Delivery API for that session. - Feature Toggling: The
editorial_featuresquery parameter controls the state of editorial features. A value of?editorial_features=enabled(case-insensitive) enables these features, while any other value disables them. The state is stored in the session asEditorialFeatureswith values of either"Enabled"or"Disabled".
This middleware is essential for a flexible preview and QA workflow, as it decouples the running application from a single, hard-coded Contentful environment.
Error handling
Error handling is bifurcated based on the hosting environment.
- Development:
app.UseDeveloperExceptionPage()is used. This middleware catches unhandled exceptions and displays a detailed HTML page containing stack traces, query parameters, headers, and other rich debugging information. - Production:
app.UseExceptionHandler("/Error")is used. This catches unhandled exceptions and re-executes the request pipeline with a new path:/Error. This prevents leaking sensitive information.
The /Error Razor Page is backed by the ErrorModel. Its OnGet handler retrieves the exception that occurred and inspects it.
csharp
// In Error.cshtml.cs
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
// Get the last exception that occurred.
var ex = HttpContext?.Features?.Get<IExceptionHandlerFeature>()?.Error;
// Special handling for Contentful API errors
if(ex is ContentfulException)
{
ContentfulException = ex as ContentfulException;
RequestId = ContentfulException.RequestId;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Note the special handling for ContentfulException. If the error originated from the Contentful SDK, the model extracts the Contentful-specific RequestId. This ID is critical when reporting API issues to Contentful support for investigation.
Status code pages
In addition to handling exceptions, the application also handles non-success HTTP status codes that do not throw an exception (e.g., 404 Not Found). This is configured with app.UseStatusCodePagesWithReExecute("/Error").
When a middleware further down the line (like MVC) produces a 4xx or 5xx status code without writing to the response body, this component will re-execute the request to the /Error path. This ensures that users see a consistent, styled error page for both 404s and 500s, rather than a blank page or a browser-default error.
Middleware ordering
The order of middleware registration in Startup.Configure is non-negotiable and dictates the application's behavior. An incorrect order can lead to security vulnerabilities, broken features, or performance degradation.
Key ordering principles in this application include:
UseExceptionHandler/UseDeveloperExceptionPagemust be first, to catch any exceptions thrown by later middleware.UseStaticFilesshould be early in the pipeline. This allows requests for assets to be served quickly without incurring the overhead of session or localization logic.UseSessionmust be called before any middleware that accessesHttpContext.Session. In this app, this includesUseRequestLocalizationandUseDeeplinks.UseRequestLocalizationmust be called beforeUseMvcandUseBreadcrumbs. This ensures that the correctCultureInfois set on the request thread before any views are rendered or any localized strings are needed.UseMvcmust be last. It is the terminal middleware that handles requests that have not been handled by any preceding component.
| Correct Order | Incorrect Order | Consequence of Incorrect Order |
|---|---|---|
app.UseSession();app.UseRequestLocalization(...); | app.UseRequestLocalization(...);app.UseSession(); | UseRequestLocalization would throw an exception because it cannot access the session to persist the user's locale. |
app.UseStaticFiles();app.UseMvc(...); | app.UseMvc(...);app.UseStaticFiles(); | Requests for static files (e.g., main.css) would needlessly pass through the entire MVC routing system, adding latency. |
app.UseExceptionHandler("/Error");app.UseMvc(...); | app.UseMvc(...);app.UseExceptionHandler("/Error"); | An unhandled exception inside an MVC action would not be caught, resulting in an unhandled 500 error sent to the client. |