Exam Area: Content Area 5 – Content Models (25%)
Reference: https://docs.developers.optimizely.com/content-management-system/docs/event-management
Create/Edit Content
│
├── SavingContent (before)
├── SavedContent (after)
│
├── PublishingContent (before)
├── PublishedContent (after)
│
├── DeletingContent (before)
└── DeletedContent (after)
| Event | Trigger |
|---|---|
LoadingContent | Before loading content |
LoadedContent | After loading content |
LoadingChildren | Before loading children |
LoadedChildren | After loading children |
SavingContent | Before saving |
SavedContent | After saving |
PublishingContent | Before publishing |
PublishedContent | After publishing |
DeletingContent | Before deleting |
DeletedContent | After deleting |
MovingContent | Before moving |
MovedContent | After moving |
RequestingApproval | Requesting approval |
RejectingContent | Rejecting content |
CheckingInContent | Checking in content |
CheckedInContent | After check in |
private void OnSaving(object sender, ContentEventArgs e)
{
switch (e.Action)
{
case SaveAction.Publish:
// Publishing
break;
case SaveAction.Save:
// Draft save
break;
case SaveAction.CheckOut:
// Checkout for editing
break;
case SaveAction.CheckIn:
// Check in
break;
case SaveAction.RequestApproval:
// Send request for approval
break;
case SaveAction.Reject:
// Reject
break;
case SaveAction.SchedulePublish:
// Scheduled publish
break;
case SaveAction.ForceCurrentVersion:
// Force version
break;
}
}
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ContentEventHandlers : IInitializableModule
{
private IContentEvents _events;
public void Initialize(InitializationEngine context)
{
_events = context.Locate.Advanced.GetInstance<IContentEvents>();
_events.PublishedContent += OnPublished;
_events.SavingContent += OnSaving;
_events.MovedContent += OnMoved;
_events.DeletedContent += OnDeleted;
}
public void Uninitialize(InitializationEngine context)
{
_events.PublishedContent -= OnPublished;
_events.SavingContent -= OnSaving;
_events.MovedContent -= OnMoved;
_events.DeletedContent -= OnDeleted;
}
// Use case 1: Sync with external system on publish
private void OnPublished(object sender, ContentEventArgs e)
{
if (e.Content is ProductPage product)
{
// Push to product catalog API
ExternalCatalogService.SyncProduct(product);
}
}
// Use case 2: Validate before saving
private void OnSaving(object sender, ContentEventArgs e)
{
if (e.Content is ArticlePage article)
{
if (e.Action == SaveAction.Publish && string.IsNullOrWhiteSpace(article.MetaDescription))
{
e.CancelAction = true;
e.CancelReason = "Meta description is required before publishing";
}
}
}
// Use case 3: Update search index
private void OnPublished_SearchIndex(object sender, ContentEventArgs e)
{
if (e.Content is ISearchable searchable)
{
SearchIndexService.UpdateIndex(e.ContentLink);
}
}
// Use case 4: Audit log
private void OnDeleted(object sender, ContentEventArgs e)
{
AuditLogger.Log(`Content ${e.ContentLink} deleted by ${HttpContext.Current?.User?.Identity?.Name}`);
}
// Use case 5: Clear cache on move
private void OnMoved(object sender, ContentEventArgs e)
{
CacheManager.RemoveByContentLink(e.ContentLink);
}
}
// Distinguish create vs save:
private void OnSaving(object sender, ContentEventArgs e)
{
var isNew = e.ContentLink == ContentReference.EmptyReference;
if (isNew)
{
// New content - set default values
if (e.Content is ArticlePage article)
{
article.Author = GetCurrentUser();
}
}
}
// ContentEvents is the concrete class (not used directly)
// IContentEvents is the interface to use
public class MyHandler
{
private readonly IContentEvents _contentEvents;
public MyHandler(IContentEvents contentEvents) // Inject interface
{
_contentEvents = contentEvents;
}
}
// IRemoteEventService for cross-server events
public class MyRemoteEventHandler
{
private readonly IRemoteEventService _remoteEventService;
public MyRemoteEventHandler(IRemoteEventService remoteEventService)
{
_remoteEventService = remoteEventService;
}
public void BroadcastCacheInvalidation(ContentReference contentLink)
{
// Send event to all server instances
_remoteEventService.RaiseEvent(
"CacheInvalidation",
new CacheEventData { ContentLink = contentLink });
}
}
PublishingContent vs PublishedContent - when to use each? (Publishing: before = can cancel; Published: after = cannot cancel)e.CancelAction = true; e.CancelReason = "...")e.Action in SavingContent? (To distinguish Save draft vs Publish vs CheckIn)IInitializableModule.Initialize())Uninitialize()? (Module may restart; avoids duplicate handlers)