Skip to main content

Outline

At a glance:
  • Read vs. Write: Distinguish between IContentLoader for optimized caching and IContentRepository for full lifecycle CRUD operations
  • Taxonomy Control: Use CategoryRepository for hierarchical organization, ensuring writable clones are used for any programmatic updates
  • Binary Assets: Manage media via MediaData and the Blob API to separate metadata from physical storage in PaaS environments
  • PaaS Efficiency: Prioritize bulk loading methods and proper security scoping to maintain high-performance architectural governance

Optimizely CMS 12 provides a suite of Repository APIs that serve as the technical foundation for programmatic data interaction. These APIs handle hierarchical content, taxonomy (categories), multi-site configurations, and binary assets (media). For enterprise PaaS environments, understanding the distinction between read-only loaders and full-access repositories is critical for maintaining high performance and ensuring architectural governance.

1. Content Repositories: IContentLoader vs. IContentRepository

The core content engine is split into two primary interfaces to optimize the request pipeline. Both handle IContent objects, but their intended technical roles differ.

Read-Only Performance: IContentLoader

IContentLoader is the primary interface for frontend delivery and data retrieval.

  • Caching Participation: Deeply integrated with the Optimizely object cache (ISynchronizedObjectInstanceCache), meaning repeated reads of the same content do not hit the database.
  • Language Handling: Provides robust support for CultureInfo and fallback mechanisms such as LanguageLoaderOption.FallbackWithMaster().
  • Type Filtering: Automatically filters returned collections to match the requested generic type, preventing TypeMismatchException.

Full Lifecycle Management: IContentRepository

IContentRepository extends the loader to include full CRUD operations (Create, Update, Delete). Write operations involve database transactions, search indexing triggers, and cache invalidation - use IContentLoader for reads to avoid this overhead.

Note: Content objects returned by the repository are read-only by default. Always call .CreateWritableClone() before modifying any property - attempting to modify a read-only instance throws an EPiServerException at runtime.

C#
public class ProductService { private readonly IContentLoader _contentLoader; private readonly IContentRepository _contentRepository; public ProductService(IContentLoader loader, IContentRepository repository) { _contentLoader = loader; _contentRepository = repository; } public void ProcessUpdate(ContentReference link) { // READ: Using optimized loader (cache-backed) var product = _contentLoader.Get<ProductPage>(link); // WRITE: Writable clone required for modifications var writableProduct = product.CreateWritableClone() as ProductPage; writableProduct.Price = 99.99m; _contentRepository.Save(writableProduct, SaveAction.Publish, AccessLevel.NoAccess); } }

2. Taxonomy Management: CategoryRepository

The CategoryRepository manages the site's system-wide taxonomy. Categories are structural nodes used for filtering and organizing large content sets across all page types.

Programmatic Patterns

  • Retrieval: Categories can be accessed by ID or Name. Retrieve categories in controllers or services rather than directly in views to keep rendering logic clean.
  • Modifiability: Like content pages, categories returned by the repository are read-only. Modifying a category requires calling .CreateWritableClone() first.
  • Parent-Child Updates: When adding a new category, the system does not automatically refresh previously loaded parent collections. The parent must be reloaded to reflect the updated hierarchy.
C#
public void CreateCustomCategory(string categoryName) { var repository = ServiceLocator.Current.GetInstance<CategoryRepository>(); var parent = repository.Get("IndustryRelated"); // Create new instance with parent reference var newCat = new Category(parent, categoryName) { Selectable = true, Available = true }; repository.Save(newCat); }

3. Multi-site Infrastructure: SiteDefinitionRepository

In enterprise PaaS environments, multiple brands or locales often coexist in a single instance. The SiteDefinitionRepository manages the technical boundaries between these sites.

Strategic Site Access

While site definitions are predominantly configured via the CMS Admin UI, developers interact with the repository to resolve context-aware logic:

  • SiteDefinition.Current: The standard shorthand to retrieve the settings (StartPage, RootPage, hostname) for the current incoming request.
  • Multi-site Context: When building background jobs or scheduled tasks that run outside a web request, use the repository to programmatically iterate through all registered sites rather than relying on SiteDefinition.Current, which will be null in that context.

4. Media and Asset Management: MediaData and the Blob API

Media in CMS 12 is treated as a specialized content type inheriting from MediaData or ImageData. Binary data is not stored in the SQL database - it is offloaded to configured BLOB providers such as Azure Blob Storage.

Programmatic Media Creation

Creating media assets programmatically requires combining content creation with binary persistence using the .SaveBlob() method. The three-step pattern below separates metadata from binary data correctly.

C#
public ContentReference UploadSiteLogo(Stream fileStream, string fileName, ContentReference folderLink) { // 1. Create content instance of specific Media Type var logo = _contentFactory.CreateContent(typeof(ImageFile)) as ImageFile; logo.Name = fileName; // 2. Persist binary stream to BLOB storage and link to content logo.BinaryData = _contentRepository.SaveBlob(fileStream, fileName); // 3. Persist metadata to the content repository return _contentRepository.Save(logo, SaveAction.Publish, AccessLevel.NoAccess, folderLink); }

5. Performance and Governance Best Practices for PaaS

High-performance PaaS environments require strict adherence to repository usage standards to ensure scalability.

  • Prefer IContentLoader: Always default to the loader for frontend components. Reserve IContentRepository for write operations only.
  • Bulk Loading: Use GetItems() when retrieving a collection of references rather than looping with individual Get() calls. Bulk loading enables batched cache lookups and reduces database round trips.
  • Security Scoping: Always apply an appropriate AccessLevel during repository operations to match the execution context - do not default to AccessLevel.NoAccess in frontend contexts.
  • Projections over Content: In search implementations, project results into ViewModels to avoid loading full IContent objects unnecessarily - search results often only need a subset of properties.

Pro tip: A common performance bottleneck on content-heavy pages is N+1 loading - a loop that calls Get() inside a foreach. Always collect the references first, then call GetItems() once.

Conclusion

The Repository APIs in Optimizely CMS 12 define the standard contract for interacting with the platform's data. By distinguishing between read-optimized loaders and full-lifecycle repositories, technical teams can build architectures that are both flexible and highly performant. Strategic implementation of CategoryRepository for taxonomy and the Blob API for media ensures that enterprise solutions remain scalable and maintainable across multi-site PaaS deployments.