📋 Content Models
Repository Services
📖 Docs

Repository Services - IContentRepository, IContentLoader - Optimizely CMS 12

Exam Area: Content Area 5 – Content Models (25%)
Source: https://academy.optimizely.com/student/path/3128969/activity/4970329 | /4970358
Published: Feb 17 / Feb 25, 2026


Overview (Academy Key Points)

  • Read vs. Write: Distinguish between IContentLoader (caching, read-only) and IContentRepository (full CRUD).
  • CreateWritableClone(): Content instances are immutable snapshots — must clone before modifying.
  • Bulk Loading: Use GetItems() instead of a Get() loop for optimal caching.
  • Taxonomy Control: CategoryRepository manages hierarchical categories; writable clone needed to update.
  • Media: MediaData + Blob API to separate binary storage from the SQL database.

1. IContentLoader (Read-Only – Preferred for Frontend)

Key points from Academy (4970329 / 4970358):

using EPiServer;
using EPiServer.Core;

public class ContentService
{
    private readonly IContentRepository _contentRepository;
    private readonly IContentLoader _contentLoader;

    // ✅ PRINCIPLE: Use IContentLoader for reads; IContentRepository for writes
    public ContentService(IContentRepository contentRepository, IContentLoader contentLoader)
    {
        _contentRepository = contentRepository;
        _contentLoader = contentLoader;
    }

    // Load single content by reference
    public ArticlePage GetArticle(ContentReference link)
    {
        return _contentLoader.Get<ArticlePage>(link);
    }

    // Load children
    public IEnumerable<ArticlePage> GetChildren(ContentReference parentLink)
    {
        return _contentLoader.GetChildren<ArticlePage>(parentLink);
    }

    // GetItems – BULK LOADING (more efficient than loop Get())
    public IEnumerable<IContent> BulkLoad(IList<ContentReference> references)
    {
        return _contentLoader.GetItems(
            references,
            new LoaderOptions { LanguageLoaderOption.FallbackWithMaster() }
        );
    }

    // Get ancestors (for breadcrumbs)
    public IEnumerable<IContent> GetAncestors(ContentReference contentLink)
    {
        return _contentLoader.GetAncestors(contentLink);
    }

    // Language-specific loading
    public ArticlePage GetInLanguage(ContentReference link, string lang)
    {
        return _contentLoader.Get<ArticlePage>(link, new CultureInfo(lang));
    }

    // TryGet - no exception if not found
    public bool TryGetArticle(ContentReference link, out ArticlePage article)
    {
        return _contentLoader.TryGet(link, out article);
    }
}

LanguageLoaderOption (Academy – 4970330):

// FallbackWithMaster: return master language if requested lang not found
_contentLoader.Get<PageData>(link, new LoaderOptions { LanguageLoaderOption.FallbackWithMaster() });

// MasterLanguage: always retrieve master version
_contentLoader.Get<PageData>(link, new LoaderOptions { LanguageLoaderOption.MasterLanguage() });

2. IContentRepository (Full CRUD – Academy 4970329 / 4970358)

Overhead Consideration: Write operations involve database transactions, search indexing triggers, and cache invalidation. Use IContentLoader for reads to avoid unnecessary transaction overhead.

public void ProcessUpdate(ContentReference link)
{
    // READ: Using optimized loader
    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);
}

Create new content:

public ContentReference CreateArticle(ContentReference parentLink)
{
    var article = _contentRepository.GetDefault<ArticlePage>(parentLink);
    article.Name = "New Article";
    article.Heading = "My Heading";
    
    return _contentRepository.Save(article, SaveAction.Publish, AccessLevel.NoAccess);
}

3. CreateWritableClone – Critical Rule (Academy – 4970358)

// ⚠️ CRITICAL: Content instances from the repository are immutable snapshots
// Must call CreateWritableClone() before modifying – MANDATORY

// ❌ WRONG - will throw InvalidOperationException
var page = _contentLoader.Get<ArticlePage>(link);
page.Heading = "New Heading";  // ❌ Exception!

// ✅ CORRECT
var page = _contentRepository.Get<ArticlePage>(link);  // Load via repository for write intent
var writablePage = page.CreateWritableClone() as ArticlePage;
writablePage.Name = `Updated Start Page - ${DateTime.Now:yyyyMMddHHmmss}`;
writablePage.MainBody = `Programmatically updated at ${DateTime.Now}.`;

// SaveAction.Publish publishes immediately; adjust to match workflow needs
_contentRepository.Save(writablePage, SaveAction.Publish, AccessLevel.Publish);

4. CategoryRepository – Taxonomy Management (Academy – 4970329)

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

Notes:


5. SaveAction Enum

SaveAction.Save              // Save as draft
SaveAction.Publish           // Publish immediately
SaveAction.CheckIn           // Check in
SaveAction.CheckOut          // Checkout for editing
SaveAction.RequestApproval   // Request approval
SaveAction.ForceCurrentVersion // Force version update
SaveAction.SchedulePublish   // Schedule for future publish

6. IContentVersionRepository

public class VersionService
{
    private readonly IContentVersionRepository _versionRepository;

    public VersionService(IContentVersionRepository versionRepository)
    {
        _versionRepository = versionRepository;
    }

    // Get all versions
    public IEnumerable<ContentVersion> GetVersions(ContentReference link)
    {
        return _versionRepository.List(link);
    }

    // Get published version
    public ContentVersion GetPublished(ContentReference link)
    {
        return _versionRepository.Load(link, LanguageSelector.AutoDetect());
    }

    // Delete specific version
    public void DeleteVersion(ContentReference versionLink)
    {
        _versionRepository.Delete(versionLink);
    }

    // Set version as published
    public void SetPublished(ContentReference versionLink)
    {
        _versionRepository.SetCommonDraft(versionLink);
    }
}

7. Getting Published vs Draft

// Load published version (default behavior)
var published = _contentLoader.Get<ArticlePage>(link);

// Load specific version
var draft = _contentLoader.Get<ArticlePage>(
    new ContentReference(link.ID, versionId));

// Load latest draft (workID = 0 gets published)
var withVersionParams = new ContentReference(link.ID, 0, link.ProviderName);

8. ContentReference Utilities

// Common ContentReference constants
ContentReference.RootPage           // Root page (ID=1)
ContentReference.StartPage          // Start page
ContentReference.WasteBasket        // Recycle bin
ContentReference.GlobalBlockFolder  // Shared blocks folder
ContentReference.SiteBlockFolder    // Site blocks folder

// Empty check
ContentReference.IsNullOrEmpty(link)

// Create new reference
var link = new ContentReference(pageId: 123);
var withVersion = new ContentReference(pageId: 123, versionId: 456);

9. Language Selector

// AutoDetect based on current request language
LanguageSelector.AutoDetect()
LanguageSelector.AutoDetect(fallbackToMaster: true)

// Specific language
new LanguageSelector("en")
new LanguageSelector("sv")

// Master language
LanguageSelector.MasterLanguage()

// Load in specific language
var swedishPage = _contentLoader.Get<ArticlePage>(link, new LanguageSelector("sv"));

10. Performance Best Practices (Academy – 4970329 / 4970358)


Review Questions

  1. What is the difference between IContentLoader and IContentRepository? (Loader: read-only, cached; Repository: full CRUD with transaction overhead)
  2. Why must you call CreateWritableClone()? (Content objects are immutable snapshots when read from cache)
  3. SaveAction.Publish vs SaveAction.Save? (Publish: immediately public; Save: draft visible to editors only)
  4. How do you load content in a specific language? (CultureInfo or LanguageLoaderOption.FallbackWithMaster())
  5. How is GetItems() more efficient than a Get() loop? (Batch caching optimization – fewer database roundtrips)
  6. What pattern does CategoryRepository require for updating a category? (CreateWritableClone() like IContent)