Appearance
Are you an LLM? You can read better optimized documentation at /configuration/services.md for this page in Markdown format
Services
This document provides a detailed technical overview of the custom services registered in the application's dependency injection (DI) container. These services form the backbone of the application's business logic, handling everything from Contentful API configuration to user state management.
Service Registration
Service registration occurs within the ConfigureServices method in Startup.cs. This method is the central hub for configuring the application's services for the ASP.NET Core dependency injection container. The application follows standard DI patterns, registering services with specific lifetimes (Singleton, Scoped, Transient) to manage their state and resource consumption effectively.
Key services are registered as follows:
csharp
// TheExampleApp/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Provides access to the current HttpContext from within Singleton services.
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Registers standard Contentful services from the SDK.
services.AddContentful(Configuration);
// Conditional registration for staging environments.
if (CurrentEnvironment.IsStaging())
{
// ... custom HttpClient registration ...
}
// Custom service to manage Contentful options dynamically.
services.AddSingleton<IContentfulOptionsManager, ContentfulOptionsManager>();
// Custom factory for the main ContentfulClient.
services.AddTransient<IContentfulClient, ContentfulClient>((ip) => {
// ... client instantiation logic ...
});
// Custom localization service.
services.AddSingleton<IViewLocalizer, JsonViewLocalizer>();
// Service to track visited lessons via cookies.
services.AddTransient<IVisitedLessonsManager, VisitedLessonsManager>();
// Service to modify breadcrumbs generated by middleware.
services.AddTransient<IBreadcrumbsManager, BreadcrumbsManager>();
// ... other framework services (MVC, Session, etc.) ...
}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
For a foundational understanding of dependency injection in ASP.NET Core, refer to the Dependency Injection architecture document.
IContentfulOptionsManager
The IContentfulOptionsManager is a critical service responsible for dynamically providing ContentfulOptions at runtime. This allows the application to switch Contentful spaces and API keys on a per-user-session basis, a feature primarily used for previewing content with different credentials.
Implementation Details:
- Registration: It is registered as a Singleton.
- Mechanism: The manager uses the injected
IHttpContextAccessorto access the current user's session. It checks the session for a serializedContentfulOptionsobject.- If session options are found, they are deserialized and returned. This is how "deeplinking" from the Contentful UI overrides the application's default settings.
- If no options are found in the session, it falls back to the default
ContentfulOptionsloaded fromappsettings.jsonduring startup.
- Usage: The custom
IContentfulClientfactory depends onIContentfulOptionsManagerto get the correct options for the current request before creating a client instance.
csharp
// TheExampleApp/Configuration/ContentfulOptionsManager.cs
public class ContentfulOptionsManager : IContentfulOptionsManager
{
private ContentfulOptions _options;
private readonly IHttpContextAccessor _accessor;
public ContentfulOptionsManager(IOptions<ContentfulOptions> options, IHttpContextAccessor accessor)
{
_options = options.Value; // Default options from configuration
_accessor = accessor;
}
// This property provides the magic. It checks session first, then falls back.
public ContentfulOptions Options {
get {
var sessionString = _accessor.HttpContext.Session.GetString(nameof(ContentfulOptions));
if (!string.IsNullOrEmpty(sessionString))
{
return JsonConvert.DeserializeObject<ContentfulOptions>(sessionString);
}
return _options;
}
}
// Detects if the current session is using credentials different from the defaults.
public bool IsUsingCustomCredentials
{
get
{
var sessionString = _accessor.HttpContext.Session.GetString(nameof(ContentfulOptions));
if (string.IsNullOrEmpty(sessionString))
{
return false;
}
var options = JsonConvert.DeserializeObject<ContentfulOptions>(sessionString);
return (options.SpaceId == _options.SpaceId &&
options.UsePreviewApi == _options.UsePreviewApi &&
options.DeliveryApiKey == _options.DeliveryApiKey &&
options.PreviewApiKey == _options.PreviewApiKey) == false;
}
}
}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
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
Key Properties:
Options: Returns either the session-stored options (if present) or the default configuration options. This allows dynamic credential switching on a per-session basis.IsUsingCustomCredentials: A boolean property that indicates whether the current session is using custom credentials different from the application's default configuration. This is useful for UI logic to display notifications when users are previewing content with alternate credentials.
This design enables powerful features like live preview and multi-space support without requiring application restarts. For more context, see Contentful Setup.
IBreadcrumbsManager
The breadcrumb functionality is a two-part system: middleware for generation and a service for modification. The IBreadcrumbsManager is the service component that allows page-specific logic to refine the breadcrumbs.
Implementation Details:
- Middleware (
Breadcrumbs): First, theUseBreadcrumbsmiddleware runs on every request. It parses the URL path, splits it into segments, and creates a preliminary list ofBreadcrumbobjects. This list is stored inHttpContext.Items["breadcrumbs"]. - Service (
BreadcrumbsManager): TheIBreadcrumbsManageris registered as Transient. It can be injected into Razor Pages or other services. Its primary purpose is to replace placeholder labels in the breadcrumbs (which are typically URL slugs) with dynamic, human-readable titles (e.g., from a Contentful entry).
csharp
// TheExampleApp/Configuration/Breadcrumbs.cs
public class BreadcrumbsManager : IBreadcrumbsManager
{
private readonly IHttpContextAccessor _accessor;
private List<Breadcrumb> _crumbs;
public BreadcrumbsManager(IHttpContextAccessor accessor)
{
_accessor = accessor;
// Retrieves the breadcrumbs generated by the middleware for this request.
_crumbs = accessor.HttpContext.Items["breadcrumbs"] as List<Breadcrumb>;
}
/// <summary>
/// Replaces the label of a breadcrumb for a specific slug with a new label.
/// </summary>
public void ReplaceCrumbForSlug(string slug, string label)
{
if(_crumbs.Any(c => c.Label == slug))
{
_crumbs.First(c => c.Label == slug).Label = label;
}
}
}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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Typical Workflow:
- A request for
/courses/my-first-coursearrives. - The
Breadcrumbsmiddleware creates breadcrumbs:Home > courses > my-first-course. - The Razor Page for the course fetches the course entry from Contentful.
- The page injects
IBreadcrumbsManagerand callsReplaceCrumbForSlug("my-first-course", "My First Awesome Course"). - The view renders the final, user-friendly breadcrumbs:
Home > courses > My First Awesome Course.
Further details on the middleware pipeline can be found in the Middleware documentation.
IVisitedLessonsManager
This service provides a simple mechanism to track which lessons and courses a user has viewed. It persists this state across requests using a browser cookie.
Implementation Details:
- Registration: It is registered as Transient. A new instance is created for each request, ensuring it reads the most up-to-date cookie information from the incoming
HttpRequest. - State Storage: It uses a cookie named
ContentfulVisitedLessons. The cookie's value is a semicolon-separated string of visited lesson/course IDs. - Constructor: When instantiated, the manager reads the cookie from the
IHttpContextAccessorand populates its publicVisitedLessonslist. AddVisitedLessonMethod: This method adds a new ID to the list and immediately updates the browser cookie, setting a 7-day expiration.
csharp
// TheExampleApp/Configuration/VisitedLessons.cs
public class VisitedLessonsManager: IVisitedLessonsManager
{
private readonly string _key = "ContentfulVisitedLessons";
private readonly IHttpContextAccessor _accessor;
public List<string> VisitedLessons { get; private set; }
public VisitedLessonsManager(IHttpContextAccessor accessor)
{
VisitedLessons = new List<string>();
_accessor = accessor;
var cookie = _accessor.HttpContext.Request.Cookies[_key];
if (!string.IsNullOrEmpty(cookie))
{
// Populates the list from the cookie on instantiation.
VisitedLessons = new List<string>(cookie.Split(';', StringSplitOptions.RemoveEmptyEntries));
}
}
public void AddVisitedLesson(string lessonId)
{
VisitedLessons.Add(lessonId);
var options = new CookieOptions
{
HttpOnly = true,
Expires = DateTime.Now.AddDays(7)
};
// Appends the updated list back to the response cookie.
_accessor.HttpContext.Response.Cookies.Append(_key, string.Join(';', VisitedLessons.Distinct()), options);
}
}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
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
Deeplinker Middleware
The Deeplinker is not a service registered in the DI container but rather a piece of middleware. Its purpose is to intercept incoming requests and check for specific query string parameters that allow a user (typically a content editor) to alter the Contentful configuration for their session.
Functionality:
- Credential Overriding: It looks for
space_id,preview_token, anddelivery_tokenin the query string. If all three are present, it:- Creates a new
ContentfulOptionsobject with the provided credentials - Validates the credentials using
ValidationContextand theSelectedOptionsvalidation model - If validation fails: Stores the validation errors and attempted options in the session, redirects to
/settings, and adds no-cache headers to the response - If validation succeeds: Serializes the new options into the user's session, which
IContentfulOptionsManagerthen picks up for subsequent Contentful API calls
- Creates a new
- API Switching: It checks for the
apiquery parameter. Ifapi=cpa, it togglesUsePreviewApitotrue, allowing the user to view draft content from the Contentful Preview API. This works both with full credential overriding or on its own using existing credentials. - Editorial Features: It checks for the
editorial_featuresquery parameter. If set toenabled, it stores "Enabled" in the session; if present with any other value, it stores "Disabled". This session flag can be used to show or hide UI elements specifically for content editors.
This middleware is crucial for enabling a seamless content preview experience directly from the Contentful web app. See the Middleware documentation for more on its place in the request pipeline.
IViewLocalizer
The application uses a custom implementation of IViewLocalizer to handle internationalization (i18n).
Implementation Details:
- Registration:
services.AddSingleton<IViewLocalizer, JsonViewLocalizer>(); - Function: The
JsonViewLocalizerservice is responsible for loading and serving translated strings from embedded JSON resource files (e.g.,en-US.json,de-DE.json). - Lifetime: It is registered as a Singleton because the translation dictionaries are static and can be loaded once and cached in memory for the lifetime of the application, providing high performance.
- Usage: Other components, like the
Breadcrumbsmiddleware, injectIViewLocalizerto translate static UI text, such as the "Home" label in the breadcrumb trail.
csharp
// TheExampleApp/Configuration/Breadcrumbs.cs (Usage Example)
// The localizer is injected into the middleware's constructor.
public Breadcrumbs(RequestDelegate next, IViewLocalizer localizer)
{
_next = next;
_localizer = localizer;
}
public async Task Invoke(HttpContext context)
{
// ...
// The localizer is used to get the translated string for "homeLabel".
items.Add(new Breadcrumb { Label = _localizer["homeLabel"].Value, Path = "/" });
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
For a complete overview of the localization strategy, please see the Internationalization documentation.
Service Lifetimes
Choosing the correct service lifetime is essential for application stability and performance. The application uses all three standard lifetimes, and the choice for each service is deliberate.
| Lifetime | When to Use | Application Examples |
|---|---|---|
| Singleton | For services that are stateless or whose state should be shared across the entire application for its lifetime. Use with caution if the service has dependencies or state that are not thread-safe. | IContentfulOptionsManager, IViewLocalizer. These services hold configuration or cached data that is safe to share across all requests. |
| Scoped | For services whose state should be maintained for the duration of a single HTTP request. A new instance is created once per request. | The ContentfulClient is effectively scoped. Although registered as Transient, its factory ensures it is configured based on request-specific data (from the session), making its behavior scoped to the request. |
| Transient | For lightweight, stateless services, or services whose state must be initialized based on the exact moment of the request. A new instance is created every time the service is requested from the container. | IVisitedLessonsManager, IBreadcrumbsManager. Both need to be new for each injection to correctly read request-specific data (from cookies or HttpContext.Items) that may have been set earlier in the request pipeline. |
For more details, refer to the official ASP.NET Core documentation on Dependency Injection Lifetimes and our internal Dependency Injection architecture document.
HttpClient Configuration
The application employs a sophisticated strategy for configuring HttpClient, particularly for accommodating different deployment environments like Staging.
In the Staging environment, a custom HttpClient is registered with a special message handler, StagingMessageHandler.
Implementation Details:
- Conditional Registration: The registration is wrapped in an
if (CurrentEnvironment.IsStaging())block inStartup.cs. StagingMessageHandler: This customHttpClientHandlerintercepts every outgoingHttpRequestMessage. It uses a regular expression to find and replace the production Contentful hostname (contentful) with a staging-specific hostname read from theStagingHostenvironment variable.- DI Integration: This custom
HttpClientis registered as a singleton. TheIContentfulClientfactory then resolves thisHttpClientinstance, automatically routing all Contentful API calls to the staging endpoint when the application is running in the Staging environment.
csharp
// TheExampleApp/Startup.cs
// In ConfigureServices:
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);
});
}
}
// The custom handler that rewrites the request URI.
public class StagingMessageHandler : HttpClientHandler
{
public string StagingHost { get; set; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var regex = new Regex("contentful");
var req = regex.Replace(request.RequestUri.ToString(), StagingHost, 1);
request.RequestUri = new Uri(req);
return await base.SendAsync(request, cancellationToken);
}
}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
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
This pattern is a clean and effective way to manage environment-specific API endpoints without cluttering the core business logic with conditional checks.