Send and receive custom events
Define, publish, and subscribe to custom events in Optimizely CMS 13 using the typed event API for in-process and load-balanced distribution.
Use the typed event API to define, publish, and subscribe to custom events in Optimizely Content Management System (CMS). This API 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
Register custom event types at startup using the AddCmsEventType<T> extension method on IServiceCollection. Each event type that supports remote distribution requires a unique GUID identifier for cross-instance routing. Provide the identifier directly through AddCmsEventType<T> or by decorating the event data class with EventDataAttribute. Configure events intended for remote distribution with the broadcast option.
Method-provided identifier
The following example registers an event type with the identifier and broadcast settings as method arguments:
// EventData definition
public class ExampleEvent : IEventData { }
// Service registration method
public void ConfigureServices(IServiceCollection services)
{
services.AddCmsEvents();
// Registration with identity and broadcast settings as arguments
services.AddCmsEventType<ExampleEvent>(new Guid("b9846410-02e0-4a69-8b68-fd07d12264ec"), broadcast: true);
}Attribute-provided metadata
The following example declares the identifier and broadcast settings on the event type itself:
// EventData definition with identity and broadcast settings declared on the type
[EventData("b9846410-02e0-4a69-8b68-fd07d12264ec", Broadcast = true)]
public class ExampleEvent : IEventData { }
// Service registration method
public void ConfigureServices(IServiceCollection services)
{
services.AddCmsEvents();
// Registration without arguments
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 Task ClearCacheAsync(Guid id, CancellationToken cancellationToken = default) { }
}Override broadcast at publish time
If the event type does not have Broadcast = true set on its attribute, 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, passing EventPublishingOptions is not required.
Subscribe to events
Create a class that implements IEventSubscriber<T> and register it in the service container. When the event system receives an event, it calls HandleAsync on each matching subscriber.
public class CustomSaveEventSubscriber(CustomRepository repository) : IEventSubscriber<CustomSaveEvent>
{
public async Task HandleAsync(CustomSaveEvent eventData, EventContext context, CancellationToken cancellationToken = default)
{
// Only respond to broadcasted events because 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
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 for a specific event type
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 with AddCmsEventType<T>() at startup |
IEventRegistry.Get(eventGuid) | new MyEventData() |
event.Raise(raiserId, data) | IEventPublisher.PublishAsync(eventData) |
event.Raised += handler | AddCmsEventSubscriber<TEventData, TSubscriber>() at 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 19 days ago
