Skip to main content

Outline

At a glance
  • Native alignment: CMS 12 fully adopts the standard .NET Core DI container, replacing legacy structures.
  • Injection hierarchy: Constructor injection is the gold standard, while Injected<T> handles property-level needs.
  • Lifetime management: Proper use of Transient, Scoped, and Singleton prevents memory leaks and thread-safety issues.
  • Locator avoidance: ServiceLocator is deprecated for modern development to ensure testability and architectural clarity.

Optimizely CMS 12 is built upon ASP.NET Core, meaning it leverages the native .NET Dependency Injection (DI) container. Dependency injection is a fundamental architectural pattern used to achieve Inversion of Control (IoC), enabling decoupled, testable, and maintainable systems. In enterprise PaaS environments, understanding how to correctly register and consume services—while adhering to specific Optimizely patterns like Injected<T>—is essential for backend governance and system stability.

1. Core DI Framework and Service Lifetimes

Registration of custom services is primarily managed in the Startup.cs class within the ConfigureServices method. Services must be registered with an appropriate lifetime, which determines how long an object lives before the container disposes of it.

Service Lifetime Best Practices

Managing object lifecycles correctly is vital for performance and preventing memory leaks or race conditions.

  • Transient: Created every time they are requested. Ideal for lightweight, stateless services.
  • Scoped: Created once per client request (connection). Many Optimizely services, such as IContentLoader, are registered as scoped or transient to ensure thread safety within a single web request.
  • Singleton: Created the first time they are requested. All subsequent requests use the same instance. This is suitable for global configurations or cache managers that maintain state across the entire application lifecycle.
public void ConfigureServices(IServiceCollection services) { // Adding standard CMS services services.AddCms(); // Registering custom services with specific lifetimes services.AddTransient<IMyStatelessService, MyStatelessService>(); services.AddScoped<IUserContextService, UserContextService>(); services.AddSingleton<IGlobalConfigManager, GlobalConfigManager>(); }

2. Constructor Injection (The Primary Pattern)

Constructor injection is the industry standard and the most recommended pattern in Optimizely CMS 12. It makes dependencies explicit and ensures that a class cannot be instantiated without its required services.

Technical Implementation

Dependencies are provided as parameters to the constructor. The .NET DI container automatically resolves these when the class (e.g., a controller or a service) is requested.

public class ContactPageController : PageController<ContactPage> { private readonly IContentLoader _contentLoader; private readonly IMyCustomService _myService; // Explicit constructor dependencies public ContactPageController(IContentLoader contentLoader, IMyCustomService myService) { _contentLoader = contentLoader; _myService = myService; } public IActionResult Index(ContactPage currentPage) { var data = _myService.ProcessPage(currentPage); return View(data); } }

3. Property Injection and Injected<T>

Property injection allows services to be injected into public properties after the object has been constructed. This pattern is commonly used when a class has optional dependencies or when class inheritance structures make constructor injection cumbersome.

Using the [Inject] Attribute

In standard ASP.NET Core, property injection is less common, but Optimizely supports it through specific attributes. This is often used in base classes or where performance requires lazy resolution of a service.

The Injected<T> Shorthand

Optimizely provides the EPiServer.ServiceLocation.Injected<T> wrapper. It serves as a shorthand for property-level injection and is lazily resolved when the .Service property is first accessed.

using EPiServer.ServiceLocation; public class MyCustomComponent { // Lazy-loaded property injection public Injected<IContentLoader> ContentLoader { get; set; } public void Execute() { // Resolved on first access var root = ContentLoader.Service.Get<PageData>(ContentReference.RootPage); } }

4. Method Injection

Method injection involves passing a dependency directly into a specific method as a parameter. In CMS 12, this is frequently seen in middleware, action filters, or IInitializableModule implementations where the dependency is only needed for a discrete operation.

Usage in Actions

Within MVC controllers, the [FromServices] attribute allows for method-level injection, preventing the need to bloat the class constructor if a service is only required for a single action.

public IActionResult Search([FromServices] ISearchService searchService, string query) { var results = searchService.Query(query); return View(results); }

5. Avoiding the Service Locator Anti-pattern

While ServiceLocator.Current.GetInstance<T>() exists for compatibility and niche static contexts (like custom validators or legacy extensions), it is considered an anti-pattern in modern Optimizely development.

  • Opaque Dependencies: It hides the dependencies of a class, making it difficult to understand the system architecture at a glance.
  • Testing Hurdles: Mocking dependencies for unit tests becomes significantly more complex when using static service location.
  • Scope Mismanagement: In CMS 12 PaaS environments, using ServiceLocator.Current outside of a web request requires manual scope creation using CreateServiceLocatorScope to prevent data corruption.

Conclusion

Mastering dependency injection in Optimizely CMS 12 is critical for building robust enterprise applications. Organizations should mandate Constructor Injection as the default pattern to ensure architectural transparency and testability. Property Injection via Injected<T> remains a powerful tool for reducing boilerplate in complex component hierarchies, while Method Injection via [FromServices] provides granular control within specific controllers. By strictly adhering to these patterns and avoiding the ServiceLocator anti-pattern, technical teams maintain a scalable and performant PaaS footprint.