Exam Area: Content Area 3 – Website Implementation & Delivery (25%)
Source: https://academy.optimizely.com/student/path/3128969/activity/4970327
Published: Feb 17, 2026
- Native alignment: CMS 12 fully adopts the standard .NET Core DI container, replacing legacy StructureMap/Unity.
- Injection hierarchy: Constructor injection is the gold standard;
Injected<T>handles property-level injection.- Lifetime management: Proper use of Transient, Scoped, Singleton prevents memory leaks and thread-safety issues.
- Locator avoidance:
ServiceLocatoris a deprecated pattern — do not use it in new code.
CMS 12 uses Microsoft.Extensions.DependencyInjection (MS DI) instead of StructureMap/Unity as used in CMS 11.
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>();
}
| Lifetime | Description | When to use |
|---|---|---|
| Transient | Created on every request | Lightweight, stateless services |
| Scoped | Created once per client request (connection) | Per-request data; many Optimizely services such as IContentLoader use Scoped to ensure thread safety |
| Singleton | Created once, reused for the application lifetime | Global config, cache managers that hold state across the entire application lifecycle |
Best practice:
IContentLoaderis registered by Optimizely as Scoped/Transient to ensure thread safety within a single web request.
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using Microsoft.Extensions.DependencyInjection;
[InitializableModule]
[ModuleDependency(typeof(ServiceContainerInitialization))]
public class DependencyResolverInitialization : IConfigurableModule
{
public void ConfigureContainer(ServiceConfigurationContext context)
{
var services = context.Services;
// Register services
services.AddScoped<IMyService, MyService>();
services.AddTransient<IEmailSender, SmtpEmailSender>();
// Override existing service
services.AddSingleton<IContentLoader, CachedContentLoader>();
}
public void Initialize(InitializationEngine context) { }
public void Uninitialize(InitializationEngine context) { }
}
Constructor injection is the industry standard and the most recommended pattern in CMS 12. It makes dependencies explicit and ensures a class cannot be instantiated without its required services.
public class ContactPageController : PageController<ContactPage>
{
private readonly IContentLoader _contentLoader;
private readonly IMyCustomService _myService;
// Explicit constructor dependencies – DI container resolves them when the class is requested
public ContactPageController(IContentLoader contentLoader, IMyCustomService myService)
{
_contentLoader = contentLoader;
_myService = myService;
}
public IActionResult Index(ContactPage currentPage)
{
var data = _myService.ProcessPage(currentPage);
return View(data);
}
}
public class ArticlePageController : PageController<ArticlePage>
{
private readonly IContentRepository _contentRepository;
private readonly IMyService _myService;
private readonly IUrlResolver _urlResolver;
public ArticlePageController(
IContentRepository contentRepository,
IMyService myService,
IUrlResolver urlResolver)
{
_contentRepository = contentRepository;
_myService = myService;
_urlResolver = urlResolver;
}
public ActionResult Index(ArticlePage currentPage)
{
var model = _myService.CreateViewModel(currentPage);
return View(model);
}
}
Property injection allows services to be injected into public properties after the object is constructed. It is commonly used when a class has optional dependencies or when inheritance structures make constructor injection inconvenient.
Optimizely provides the EPiServer.ServiceLocation.Injected<T> wrapper — 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);
}
}
In MVC controllers, the [FromServices] attribute enables method-level injection, avoiding constructor bloat when a service is only needed in one action.
public IActionResult Search([FromServices] ISearchService searchService, string query)
{
var results = searchService.Query(query);
return View(results);
}
Although ServiceLocator.Current.GetInstance<T>() exists for compatibility, it is an anti-pattern in modern Optimizely development:
| Issue | Details |
|---|---|
| Opaque Dependencies | Hides class dependencies, making the architecture harder to understand |
| Testing Hurdles | Mocking dependencies is complex when using static service location |
| Scope Mismanagement | Using it outside a web request requires manually creating a scope with CreateServiceLocatorScope |
// ⚠️ ANTI-PATTERN – Avoid in CMS 12
var contentRepo = ServiceLocator.Current.GetInstance<IContentRepository>();
// ✅ PREFERRED – Constructor injection
public class MyService
{
private readonly IContentRepository _contentRepository;
public MyService(IContentRepository contentRepository)
{
_contentRepository = contentRepository;
}
}
IInitializableModule.Initialize() to subscribe to events (context.Locate.Advanced)public void Initialize(InitializationEngine context)
{
// Acceptable inside Initialize()
var events = context.Locate.Advanced.GetInstance<IContentEvents>();
events.SavingContent += OnSavingContent;
}
// Core content services
IContentRepository // CRUD operations
IContentLoader // Read-only (performance)
IContentVersionRepository // Version management
IContentSecurityRepository // Access control
// URL services
IUrlResolver // Content → URL
IPageRouteHelper // Route helpers
// Languages
ILanguageBranchRepository // Language management
LanguageSelector // Language selection
// Cache
IObjectInstanceCache // Object caching
ISynchronizedObjectInstanceCache // Synchronized cache
// Events
IContentEvents // CMS events (Published, Deleted, etc.)
// Visitor Groups
IVisitorGroupRepository // Visitor group management
// Scheduled Jobs
IScheduledJobRepository // Scheduled job management
// Override an existing CMS service
services.AddTransient<IContentRenderer, MyCustomContentRenderer>();
// Decorate (wrap existing)
services.Decorate<IContentLoader, CachingContentLoader>();
@inject IContentRepository ContentRepository
@inject IUrlResolver UrlResolver
@{
// Use in view
var link = UrlResolver.GetUrl(Model.ContentLink);
}
AddScoped and AddSingleton? (Scoped: per-request; Singleton: application-lifetime)Injected<T> and when should it be used? (Lazy property injection shorthand; use when constructor injection is not possible)[FromServices] used for? (Method injection for a service that is only needed in one action method)ServiceLocator an anti-pattern? (Opaque dependencies, testing difficulty, scope mismanagement)IContentLoader be registered with? (Scoped/Transient – Optimizely registers it automatically to ensure thread safety)IConfigurableModule vs IInitializableModule used for? (IConfigurableModule: register services; IInitializableModule: initialize/subscribe to events)