Skip to main content

Outline

At a glance
  • Mechanisms: Background processing and content lifecycle hooks.
  • Platform: Built on .NET Dependency Injection in CMS 12.
  • Architecture: Master scheduled jobs and IContentEvents for scalability.

Scheduled jobs and content events are the primary mechanisms for executing background tasks and orchestrating custom business logic within the content lifecycle. In Optimizely CMS 12, these components leverage the native .NET Dependency Injection container and modern middleware architecture. For technical architects, mastering these tools is essential for maintaining data integrity and system scalability in high-volume PaaS environments.

1. Architectural Overview of Scheduled Jobs

Scheduled jobs perform recurring maintenance, data synchronization, or long-running batch operations outside the scope of a standard web request. They execute within the same process as the application but run asynchronously in an anonymous context.

The ScheduledJobBase Foundation

To implement a custom job, a class must inherit from EPiServer.Scheduler.ScheduledJobBase and be decorated with the [ScheduledPlugIn] attribute. This attribute registers the job in the CMS database and makes it manageable via the Admin UI.

  • Execute() Method: The entry point for the job logic. It returns a string that appears in the "Status" column of the job history.
  • Dependency Injection: Unlike legacy versions, CMS 12 supports full constructor injection for scheduled jobs, allowing for clean service consumption.
  • Graceful Termination: For jobs processing large datasets, overriding the Stop() method is mandatory to respect administrator requests to halt execution.
[ScheduledPlugIn(DisplayName = "Archive Expired Content", GUID = "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d")] public class ContentArchivingJob : ScheduledJobBase { private bool _stopSignaled; private readonly IContentRepository _repository; public ContentArchivingJob(IContentRepository repository) { _repository = repository; } public override string Execute() { OnStatusChanged("Starting content archival..."); // Logical processing loop foreach (var item in GetItemsToArchive()) { if (_stopSignaled) return "Job stopped manually by administrator."; // Archive logic here... } return "Archival completed successfully."; } public override void Stop() { _stopSignaled = true; } }

2. Technical Governance for Jobs in PaaS

Operating scheduled jobs in a cloud environment requires specific considerations for reliability and user privileges.

Executing with Privileges

Jobs run as Anonymous by default. If an operation requires a specific role (e.g., "WebAdmins"), developers must use the IPrincipalAccessor to programmatically assign a security principal within the Execute() method to ensure appropriate access rights are enabled during the batch process.

Scalability and Multi-Instance Environments

In balanced PaaS environments, multiple site instances share a single database. To prevent race conditions, the Optimizely scheduler ensures that only one instance executes a specific job at any given time. However, developers should ensure the underlying logic is idempotent to handle rare cases of server restarts or job retries, preventing duplicate data processing.

3. Content Event Handling with IContentEvents

Content events allow developers to hook into the fundamental state changes of content items (pages, blocks, media). This is the primary method for enforcing content governance and triggering external integrations.

Structure of an Initialization Module

Event handlers are typically registered within an IInitializableModule. This ensures that subscriptions are established during application startup and cleaned up during shutdown to prevent memory leaks from dangling delegates.

[InitializableModule] [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] public class EventInitialization : IInitializableModule { public void Initialize(InitializationEngine context) { var events = context.Locate.Advanced.GetInstance<IContentEvents>(); events.SavingContent += OnSavingContent; } public void Uninitialize(InitializationEngine context) { var events = context.Locate.Advanced.GetInstance<IContentEvents>(); events.SavingContent -= OnSavingContent; // Prevent memory leaks } private void OnSavingContent(object sender, ContentEventArgs e) { // Enforce business rules if (e.Content is ArticlePage article && string.IsNullOrEmpty(article.MetaDescription)) { e.CancelAction = true; e.CancelReason = "Meta Description is required for SEO compliance."; } } }

4. Strategic Usage of Specific Events

Event Name Lifecycle Stage Primary Use Case
SavingContent Pre-persistence Data validation and blocking illegal saves.
SavedContent Post-persistence Triggering local cache invalidations.
PublishingContent Pre-approval Final business logic checks before going live.
PublishedContent Post-live External API triggers (e.g., Optimizely Graph sync).

5. Performance and Caching Best Practices

  • Asynchronous Execution: Avoid blocking the main PublishedContent thread; enqueue high-latency calls to maintain responsiveness.
  • Batching in Jobs: Use batching patterns to minimize database/network IO and reduce memory footprint.
  • Avoid Recursive Events: Utilize SaveAction.SkipValidation to prevent infinite loops when modifying content within its own event.

Conclusion

The integration of scheduled jobs and content events provides the necessary infrastructure for automating complex workflows and maintaining data standards in Optimizely CMS 12. By utilizing Constructor Injection in jobs and implementing IInitializableModule for event governance, technical teams can ensure a performant, predictable, and secured CMS architecture.