📋 Content Models
Essential Classes Data Abstraction
📖 Docs

Essential Classes - Data Abstraction and Event Model

Exam Area: Content Area 5 – Content Models (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/event-management


1. Service Locator

using EPiServer.ServiceLocation;

// Legacy approach (avoid in CMS 12)
var service = ServiceLocator.Current.GetInstance<IContentRepository>();

// Preferred in CMS 12: Constructor Injection
public class MyController : Controller
{
    private readonly IContentRepository _repo;
    
    public MyController(IContentRepository repo)
    {
        _repo = repo;
    }
}

// When you MUST use ServiceLocator (static context):
public static class StaticHelper
{
    public static IContentRepository GetRepo()
    {
        return ServiceLocator.Current.GetInstance<IContentRepository>();
    }
}

2. IContentEvents - Event Model

using EPiServer;
using EPiServer.Core;

// IContentEvents has the main events:
public interface IContentEvents
{
    // Publishing lifecycle
    event EventHandler<ContentEventArgs> PublishingContent;
    event EventHandler<ContentEventArgs> PublishedContent;
    
    // Saving lifecycle
    event EventHandler<ContentEventArgs> SavingContent;
    event EventHandler<ContentEventArgs> SavedContent;
    
    // Deleting
    event EventHandler<ContentEventArgs> DeletingContent;
    event EventHandler<ContentEventArgs> DeletedContent;
    
    // Moving to wastebasket
    event EventHandler<ContentEventArgs> MovingContent;
    event EventHandler<ContentEventArgs> MovedContent;
    
    // Loading
    event EventHandler<ContentEventArgs> LoadingContent;
    event EventHandler<ContentEventArgs> LoadedContent;
    
    // Children loading
    event EventHandler<ChildrenEventArgs> LoadingChildren;
    event EventHandler<ChildrenEventArgs> LoadedChildren;
    
    // Version status changes
    event EventHandler<ContentEventArgs> RequestingApproval;
    event EventHandler<ContentEventArgs> RejectingContent;
    event EventHandler<ContentEventArgs> CheckingInContent;
}

3. Subscribing to Events

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ContentEventHandler : IInitializableModule
{
    private IContentEvents _contentEvents;

    public void Initialize(InitializationEngine context)
    {
        _contentEvents = context.Locate.Advanced.GetInstance<IContentEvents>();
        
        // Subscribe
        _contentEvents.PublishedContent += OnPublished;
        _contentEvents.SavingContent += OnSaving;
        _contentEvents.DeletingContent += OnDeleting;
    }

    public void Uninitialize(InitializationEngine context)
    {
        // MUST unsubscribe!
        _contentEvents.PublishedContent -= OnPublished;
        _contentEvents.SavingContent -= OnSaving;
        _contentEvents.DeletingContent -= OnDeleting;
    }

    private void OnPublished(object sender, ContentEventArgs e)
    {
        var content = e.Content;
        var contentRef = e.ContentLink;
        
        // Example: Purge CDN cache
        if (content is PageData page)
        {
            PurgeCdnCache(page);
        }
    }

    private void OnSaving(object sender, ContentEventArgs e)
    {
        // Validate before saving
        if (e.Content is ArticlePage article)
        {
            if (string.IsNullOrEmpty(article.Heading))
            {
                e.CancelAction = true;
                e.CancelReason = "Heading is required!";
            }
        }
    }

    private void OnDeleting(object sender, ContentEventArgs e)
    {
        // Log deletion
        var contentRef = e.ContentLink;
        // Log...
    }
}

4. ContentEventArgs

public class ContentEventArgs : EventArgs
{
    public ContentReference ContentLink { get; set; }  // Content being acted on
    public IContent Content { get; set; }              // The content object
    public SaveAction Action { get; set; }             // What action
    public bool CancelAction { get; set; }             // Cancel the action
    public string CancelReason { get; set; }           // Reason for cancel
    public IContent RequiredRole { get; set; }         // Required role
    public string TargetLink { get; set; }             // Target (for moves)
}

5. Repository Dependency Services

// Core repositories injected via DI

// Content CRUD
IContentRepository        // Full CRUD
IContentLoader            // Read-only
IContentVersionRepository // Version management

// Content type management
IContentTypeRepository    // Content types
IPropertyDefinitionRepository // Properties

// Security
IContentSecurityRepository // ACL management

// Language
ILanguageBranchRepository  // Languages

// Scheduled Jobs
IScheduledJobRepository    // Job management

// Notification
INotificationRepository    // Notifications

6. Resolver Dependency Services

// URL and content resolution

IUrlResolver              // Content → URL
IContentRouteHelper       // Route helpers
ISiteDefinitionRepository // Site management
ISiteDefinitionResolver   // Current site

// Template resolution
ITemplateResolver         // Find templates
IContentRenderer          // Render content

// Language resolution
ILanguageSelector         // Language selection
LanguageSelector          // Concrete implementations

7. Event Cancellation

// Cancel action in a before event
private void OnPublishing(object sender, ContentEventArgs e)
{
    if (e.Content is ArticlePage article)
    {
        if (!ValidateArticle(article))
        {
            e.CancelAction = true;
            e.CancelReason = "Article validation failed: Missing required fields";
        }
    }
}

8. Event Management - Azure Service Bus

// For multi-instance: use Azure Service Bus to broadcast events
builder.Services.AddAzureServiceBusEventProvider(options =>
{
    options.ConnectionString = "your-service-bus-connection";
    options.TopicName = "cms-events";
    options.SubscriptionName = Environment.MachineName;
});

Review Questions

  1. What does IContentEvents provide? (Events for the content lifecycle: publish, save, delete, etc.)
  2. When do you use CancelAction = true? (In "before" events such as PublishingContent, SavingContent)
  3. Why MUST you unsubscribe events in Uninitialize()? (Avoids memory leaks and duplicate handler calls)
  4. How does IContentRepository differ from IContentLoader? (Repository: full CRUD; Loader: read-only, faster)
  5. How do you broadcast events in a multi-server setup? (Azure Service Bus event provider)