Dependency Injection
Outline
- 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.
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.
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.
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.
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.Currentoutside of a web request requires manual scope creation usingCreateServiceLocatorScopeto 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.
