Mastering IContentRepository
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 all 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.
IContentRepository vs IContentLoader
Select a topic to expand and read the details.
IContentRepository - when to use it ▼
Read + write. Use only when you intend to create, update, delete, or move content. Using the write-capable interface for pure reads over-declares intent and opens the door to accidental mutations.
IContentLoader - when to use it ▼
Read-only. Prefer this for queries, page rendering, navigation building, and listing content. The interface choice communicates intent clearly to reviewers and reduces the risk of accidental writes during development or refactoring.
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.
Note: If ServiceLocator is spreading across the codebase, that is usually a refactoring signal. It is 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 or cast safely.
3.1 Loading a single content item by type
Handling unknown types without exceptions
Select a pattern to expand and see the code.
Pattern matching (recommended) ▼
Load as IContent and use pattern matching. Avoids type-mismatch exceptions and keeps flow explicit.
Try-cast semantics (as keyword) ▼
Use the as keyword if you prefer a null-check approach over pattern matching.
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 content is loaded based on the current request context, which is sometimes intended and sometimes 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.
Important: If you try to modify a loaded content instance without calling CreateWritableClone(), you will either get a runtime exception or end up with changes that silently do not persist.
Choosing SaveAction deliberately
Select an action to expand and read when to use it.
SaveAction.Publish ▼
Publishes the content immediately. Use cautiously in automated jobs - publishing without human review may bypass editorial governance. Ensure this is intentional before using in scheduled tasks.
SaveAction.Save ▼
Saves as a draft. Useful when you want the content to go through a review or approval step before going live. Prefer this over Publish in import or migration jobs when editorial sign-off is required.
SaveAction.CheckIn ▼
Checks in changes. Relevant when using check-out/check-in workflows. Ensures the content lock is released after programmatic editing so other editors can continue working on the item.
SaveAction.ForceCurrentVersion ▼
Advanced and rarely needed. Overwrites the current version without creating a new one. Use only when you fully understand the versioning implications - it can silently discard version history in ways that are difficult to recover from.
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.
-
Use the right interface:
IContentLoaderfor reads,IContentRepositoryfor writes. -
Always clone before writing:
CreateWritableClone()is mandatory for updates. - Pick SaveAction intentionally: publishing in a job is very different from 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 or loader options to avoid loading the wrong language version.
-
Prefer batch loading: use
GetItemsfor efficiency and predictable performance.
Pro tip: In code reviews, the fastest signal that a developer has swapped IContentLoader for IContentRepository "just to make it work" is that no Save() call follows. Flag it - it is always worth the conversation.
Conclusion
IContentRepository, complemented by IContentLoader, is a foundational API surface for building dynamic Optimizely CMS 12 solutions.
Keep read and write intent clear, handle languages deliberately, clone before updates, and save with the correct workflow semantics. Following these principles ensures your content operations remain stable and maintainable as the solution grows.
