Send and receive custom events
Describes how to send and receive custom events in Optimizely Content Management System (CMS) using the event API. Use these APIs to raise custom in-process events or to distribute custom events across instances in a load-balanced environment.
Optimizely CMS provides a typed event system that lets you define, publish, and subscribe to custom events. This system replaces the older IEventRegistry-based approach with a strongly-typed model built around three core interfaces:
IEventData– Marker interface for your custom event data type.IEventPublisher– Publishes events to local and remote subscribers.IEventSubscriber<T>– Subscribes to events of a specific type.
Event types
Custom event types must be registered at startup using the AddCmsEventType<T> extension method on IServiceCollection. Each event type that should support remote distribution requires a unique GUID identifier for cross-instance routing. Provide the identifier directly through the AddCmsEventType<T> method or by decorating the event data class with EventDataAttribute. Events intended to be distributed remotely should be configured with the broadcast option.
Method provided identifier
// EventData definition – no identity information
public class ExampleEvent : IEventData { }
// Service registration method
public void ConfigureServices(IServiceCollection services)
{
services.AddCmsEvents();
// Registration – Identity and broadcast settings are provided as arguments
services.AddCmsEventType<ExampleEvent>(new Guid("b9846410-02e0-4a69-8b68-fd07d12264ec"), broadcast: true);
}Attribute provided metadata
// EventData definition – identity and broadcast settings are declared on the event type
[EventData("b9846410-02e0-4a69-8b68-fd07d12264ec", Broadcast = true)]
public class ExampleEvent : IEventData { }
// Service registration method
public void ConfigureServices(IServiceCollection services)
{
services.AddCmsEvents();
// Registration – no arguments needed
services.AddCmsEventType<ExampleEvent>();
}Serialization requirements
Event data types intended for distribution must be JSON-serializable so the event provider can transport them between instances. Keep event data simple (primitive types, strings, and dates) and avoid circular references or complex object graphs.
Publish events
Inject IEventPublisher into your class and call PublishAsync to raise an event:
using EPiServer.Events;
[EventData("b378eacd-41ab-4313-a411-6aa33403bb1a", Broadcast = true)]
public record CustomSaveEvent(Guid Id) : IEventData { }
public class CustomRepository(IEventPublisher eventPublisher)
{
public async Task SaveAsync(Custom instance, CancellationToken cancellationToken = default)
{
// Save the custom instance here...
// Clear local cache
await ClearCacheAsync(instance.Id, cancellationToken);
// Publish the event (broadcast behavior is determined by the attribute)
await _eventPublisher.PublishAsync(new CustomSaveEvent(instance.Id), cancellationToken);
}
public async ClearCacheAsync(Guid id, CancellationToken cancellationToken = default) { }
}Override broadcast at publish time
If the event type does not have Broadcast = true set on its attribute, you can override the broadcast behavior per-call using EventPublishingOptions:
await _eventPublisher.PublishAsync(
new CustomSaveEvent(cacheKey),
new EventPublishingOptions { Broadcast = true }
);When Broadcast is set to true on the attribute, you do not need to pass EventPublishingOptions.
Subscribe to events
To subscribe to an event, create a class that implements IEventSubscriber<T> and register it in the service container.
When the event system receives an event, it looks for registered subscribers and calls the HandleAsync method on each one.
public class CustomSaveEventSubscriber(CustomRepository repository) : IEventSubscriber<CustomSaveEvent>
{
public async Task HandleAsync(CustomSaveEvent eventData, EventContext context, CancellationToken cancellationToken = default)
{
// Only respond to broadcasted events as local saves are already handled
if (context.Broadcasted)
{
await repository.ClearLocalCacheAsync(eventData.Id);
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddCmsEvents();
services.AddCmsEventType<ExampleEvent>();
// Register subscriber
services.AddCmsEventSubscriber<ExampleEvent, ExampleEventSubscriber>();
}
Subscribe to multiple event types
It is also possible to subscribe to multiple event types using one IEventSubscriber by subscribing to a base event type.
public record CustomEvent : IEventData
{
public required Guid Id { get; init; }
}
public record CustomSaveEvent : CustomEvent { }
public record CustomDeleteEvent : CustomEvent { }
public class CustomSaveEventSubscriber(CustomRepository repository) : IEventSubscriber<CustomEvent>
{
public async Task HandleAsync(CustomEvent eventData, EventContext context, CancellationToken cancellationToken = default)
{
if (context.Broadcasted)
{
await repository.ClearLocalCacheAsync(eventData.Id);
// Additional task that should only happen if a specific event type was published
if (eventData is CustomDeleteEvent)
{
await repository.RebuildIndex();
}
}
}
}
Thread safety and event ordering
The event system does not guarantee sequential processing. Events from remote instances can arrive in any order and the system can process them concurrently. Ensure your event handlers are thread-safe and do not depend on a specific arrival order.
Migration from IEventRegistry
If you are upgrading from the older IEventRegistry API, the following table summarizes the key changes:
| Legacy API (IEventRegistry) | New API |
|---|---|
[EventsServiceKnownType] | Register type AddCmsEventType<T>() at startup |
IEventRegistry.Get(eventGuid) | new MyEventData() |
event.Raise(raiserId, data) | IEventPublisher.PublishAsync(eventData) |
event.Raised += handler | AddCmsEventSubscriber<TEventData, TSubscriber>() as startup |
OnEvent(object, EventNotificationEventArgs) | IEventSubscriber<T>.HandleAsync(T, EventContext, CancellationToken) |
Manual GUID management with RaiserId and EventId | GUID defined with EventDataAttribute on the type |
EventNotificationEventArgs.RaiserId check for filtering local events | Check EventContext.Broadcasted in HandleAsync subscriber method |
Updated 6 days ago
