🌐 Website Implementation
Ioc Container Di
📖 Docs

IOC Container and Dependency Injection - Optimizely CMS 12

Exam Area: Content Area 3 – Website Implementation & Delivery (25%)
Source: https://academy.optimizely.com/student/path/3128969/activity/4970327
Published: Feb 17, 2026


Overview (Academy Key Points)

  • 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: ServiceLocator is a deprecated pattern — do not use it in new code.

1. DI Container in CMS 12

CMS 12 uses Microsoft.Extensions.DependencyInjection (MS DI) instead of StructureMap/Unity as used in CMS 11.


2. Registering Services

In Program.cs (Startup.cs):

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>();
}

Service Lifetimes (Academy – 4970327):

LifetimeDescriptionWhen to use
TransientCreated on every requestLightweight, stateless services
ScopedCreated once per client request (connection)Per-request data; many Optimizely services such as IContentLoader use Scoped to ensure thread safety
SingletonCreated once, reused for the application lifetimeGlobal config, cache managers that hold state across the entire application lifecycle

Best practice: IContentLoader is registered by Optimizely as Scoped/Transient to ensure thread safety within a single web request.


3. IConfigurableModule (Registration in Modules)

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) { }
}

4. Injecting Services in Controllers (Constructor Injection – Preferred)

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);
    }
}

5. Property Injection and Injected<T> (Academy – 4970327)

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.

Injected<T> Shorthand:

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);
    }
}

Method Injection with [FromServices]:

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);
}

6. ServiceLocator – Anti-Pattern (Academy – 4970327)

Although ServiceLocator.Current.GetInstance<T>() exists for compatibility, it is an anti-pattern in modern Optimizely development:

IssueDetails
Opaque DependenciesHides class dependencies, making the architecture harder to understand
Testing HurdlesMocking dependencies is complex when using static service location
Scope MismanagementUsing 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;
    }
}

When ServiceLocator is still acceptable:

public void Initialize(InitializationEngine context)
{
    // Acceptable inside Initialize()
    var events = context.Locate.Advanced.GetInstance<IContentEvents>();
    events.SavingContent += OnSavingContent;
}

7. Key CMS Services (Commonly Injected)

// 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

8. Decorating/Overriding Services

// Override an existing CMS service
services.AddTransient<IContentRenderer, MyCustomContentRenderer>();

// Decorate (wrap existing)
services.Decorate<IContentLoader, CachingContentLoader>();

9. InjectionContext in Views

@inject IContentRepository ContentRepository
@inject IUrlResolver UrlResolver

@{
    // Use in view
    var link = UrlResolver.GetUrl(Model.ContentLink);
}

Review Questions

  1. Which DI framework does CMS 12 use? (Microsoft.Extensions.DependencyInjection – native .NET Core)
  2. What is the difference between AddScoped and AddSingleton? (Scoped: per-request; Singleton: application-lifetime)
  3. What is Injected<T> and when should it be used? (Lazy property injection shorthand; use when constructor injection is not possible)
  4. What is [FromServices] used for? (Method injection for a service that is only needed in one action method)
  5. Why is ServiceLocator an anti-pattern? (Opaque dependencies, testing difficulty, scope mismanagement)
  6. What lifetime should IContentLoader be registered with? (Scoped/Transient – Optimizely registers it automatically to ensure thread safety)
  7. What are IConfigurableModule vs IInitializableModule used for? (IConfigurableModule: register services; IInitializableModule: initialize/subscribe to events)