Trade-offs
Outline
-
What it is:
IContentRepositoryis the write-capable API for CRUD operations on Optimizely content. -
What to prefer for reads: Use
IContentLoaderfor read-only operations to keep intent clear and reduce accidental writes. -
Core "gotcha": Content instances are effectively immutable; update via
CreateWritableClone()+Save(). - PaaS mindset: Treat content operations as versioned data changes: language, permissions, save actions, and workflow impact matter.
Introduction
Optimizely CMS 12 provides a robust API for managing content programmatically, forming the backbone of dynamic digital experiences. At the heart of content
manipulation for developers lies the IContentRepository interface, a cornerstone for interacting with the CMS content model. This article
walks through practical usage of IContentRepository and its read-only counterpart IContentLoader, with concrete examples and
the operational considerations that matter in CMS 12 PaaS deployments.
1. Understanding IContentRepository and IContentLoader
The EPiServer.IContentRepository interface is the primary gateway for operating on objects that implement EPiServer.Core.IContent
(pages, blocks, media, and custom types). It supports full CRUD semantics and versioning-aware save behavior.
Expand: IContentRepository vs IContentLoader (the practical rule)
-
IContentRepository: read + write. Use only when you intend to create/update/delete/move content. -
IContentLoader: read-only. Prefer this for queries, page rendering, navigation building, and listing content. - Why it matters: the interface choice communicates intent and reduces accidental writes (and review surprises).
2. Getting started: obtaining repository instances
In CMS 12, dependency injection (DI) is the recommended and most maintainable way to access IContentRepository and IContentLoader.
DI keeps dependencies explicit, improves testability, and avoids hidden coupling.
2.1 Dependency injection (recommended)
2.2 ServiceLocator (alternative, use with caution)
ServiceLocator can be useful in static or legacy contexts where constructor injection is not feasible, but it hides dependencies and makes
testing harder. Treat it as a "last resort."
If you see ServiceLocator spreading across the codebase, that's usually a refactoring signal. It's convenient, but it quietly makes your
architecture harder to reason about.
3. Core operations: loading content
Loading content is the most frequent operation. When you know the type, use Get<T>. When you don't, load as IContent
and inspect/cast safely.
3.1 Loading a single content item by type
Expand: Handling unknown types without exceptions
If you don't know what's at a ContentReference, load as IContent and use pattern matching. This avoids type-mismatch
exceptions and keeps flow explicit.
If you prefer "try-cast" semantics:
3.2 Loading multiple items efficiently
For bulk loads, GetItems is typically more efficient than repeated single calls. It also pairs naturally with language options via
LoaderOptions.
3.3 Language-specific loading
CMS 12 is built for multilingual sites. Use CultureInfo or language loader options deliberately—otherwise you'll load based on the current
request context, which is sometimes what you want, and sometimes very much not.
4. Traversing the content tree: listing children
Optimizely content is hierarchical. Listing children is a foundational operation for navigation, listings, and content discovery features.
5. Writing content safely: writable clones and save actions
Modifying content programmatically requires one non-negotiable step: create a writable clone. Loaded content instances are treated as immutable snapshots.
Update the clone, then save with the correct SaveAction and required AccessLevel.
If you try to modify a loaded content instance without CreateWritableClone(), you're either going to fail at runtime or end up with
"why didn't this save?" debugging pain.
Expand: Choosing SaveAction deliberately
-
SaveAction.Publish: publish immediately (be careful in automated jobs). -
SaveAction.Save: save as draft; useful when you want review/approval steps. -
SaveAction.CheckIn: check in changes; relevant when using check-out/check-in workflows. -
SaveAction.ForceCurrentVersion: advanced/rare; use only when you fully understand versioning implications.
6. Best practices and operational considerations
These guidelines keep your content operations predictable, reviewable, and easier to maintain—especially in larger PaaS solutions where multiple teams and automated jobs touch content.
Expand: Best practices checklist
-
Use the right interface:
IContentLoaderfor reads,IContentRepositoryfor writes. -
Always clone before writing:
CreateWritableClone()is mandatory for updates. -
Pick
SaveActionintentionally: publishing in a job is very different than saving a draft for review. -
Enforce permissions: set
AccessLevelappropriately to avoid unauthorized modifications. - Handle errors explicitly: type mismatches and access denied errors should be visible and actionable in logs.
- Be language-aware: specify culture/loader options to avoid "wrong language" surprises.
-
Prefer batch loading: use
GetItemsfor efficiency and predictable performance.
Conclusion
IContentRepository, complemented by IContentLoader, is a foundational API surface for building dynamic Optimizely CMS 12 solutions.
If you keep read/write intent clear, handle languages deliberately, clone before updates, and save with the correct workflow semantics, your content
operations will remain stable and maintainable as the solution grows.
