Handle object events
Objects include all data structures used within Xperience, such as user accounts, taxonomies, contacts, or setting keys. By handling events that occur during the lifecycle of objects, you can add custom functionality to almost any part of the system. In the API, objects are represented by Info classes.
Within the context of event handling, objects do NOT include content items, website channel pages, emails or headless items.
Use the ContentItemEvents, WebPageEvents or HeadlessItemEvents classes to assign handlers for events triggered for this type of content.
Object event handlers with async support
For the most common object events that occur before or after an object is created, updated or deleted, you can implement handlers with support for both synchronous and asynchronous code. These handlers also allow you to use standard constructor dependency injection to get instances of required API services.
To add an object event handler, create a class implementing the IInfoObjectEventHandler
interface, with a generic event class parameter that determines which event type is handled. You can modify the event class with an Info class generic parameter to choose a specific object type to handle. For example:
// Class declaration for a "before update" event handler for admin user objects (UserInfo)
public class UserBeforeUpdateHandler : IInfoObjectEventHandler<InfoObjectBeforeUpdateEvent<UserInfo>>
Omit the second generic parameter if you wish to handle the event for all types of objects in the system.
The following event classes are available for the first generic parameter:
Event |
Description |
Before handler |
After handler |
Insert |
Occurs when a new object is created. |
|
|
Update |
Occurs when the data of an existing object is updated. |
|
|
Delete |
Occurs when an object is deleted. |
|
|
Before
events are triggered before the given action’s result is saved in the database, After
events occur after the action successfully completes and is saved.
For every event handler, you need to implement both of the following methods:
Handle
– invoked when the event is triggered by a synchronous operation, such as theSet
method of the corresponding InfoProvider API.HandleAsync
– invoked when the event is triggered by an async operation, such as theSetAsync
method of the corresponding InfoProvider API.
The methods provide an infoObjectEvent
parameter of the corresponding event class, which you can use to access the Info object related to the event. In many cases, your implementations of the methods will share code, which you can extract into private methods. See the Example to learn more.
Register object event handlers
Event handlers for object events must be registered in your application’s service collection by calling the AddInfoObjectEventHandler
extension method for IServiceCollection. For example, you can create a custom module with initialization code and access IServiceCollection
in the OnPreInit
override.
using CMS;
using CMS.Core;
using CMS.DataEngine;
using CMS.ContentEngine;
[assembly: RegisterModule(typeof(ObjectEventModule))]
public class ObjectEventModule : Module
{
public ObjectEventModule() : base(nameof(ObjectEventModule))
{
}
// OnPreInit allows you to access IServiceCollection via ModulePreInitParameters
protected override void OnPreInit(ModulePreInitParameters parameters)
{
base.OnPreInit(parameters);
// Registers an object event handler
parameters.Services.AddInfoObjectEventHandler<InfoObjectBeforeUpdateEvent<UserInfo>, UserBeforeUpdateHandler>();
}
}
Example
The following example demonstrates how to use event handlers to customize the behavior of a specific type of object.
The sample code extends the functionality of taxonomies by adding a handler for the “after insert” event of TaxonomyInfo
. This event occurs when a taxonomy is created. The custom logic automatically adds a default initial tag for every taxonomy.
Open your Xperience solution in Visual Studio.
Add a new class within a custom assembly (Class Library project) referenced by your project.
Make the class implement
IInfoObjectEventHandler<InfoObjectAfterInsertEvent<TaxonomyInfo>>
.Define the
Handle
andHandleAsync
methods:C#using System.Threading.Tasks; using System.Threading; using CMS.DataEngine; using CMS.ContentEngine; // Event handler triggered after a new taxonomy object is created // Creates a default initial tag for every new taxonomy public class TaxonomyInitialTagHandler : IInfoObjectEventHandler<InfoObjectAfterInsertEvent<TaxonomyInfo>> { private readonly IInfoProvider<TagInfo> tagInfoProvider; // Uses constructor dependency injection to get instances of required services public TaxonomyInitialTagHandler(IInfoProvider<TagInfo> tagInfoProvider) { this.tagInfoProvider = tagInfoProvider; } // Invoked by the synchronous Set operation public void Handle(InfoObjectAfterInsertEvent<TaxonomyInfo> infoObjectEvent) { TaxonomyInfo taxonomy = infoObjectEvent.InfoObject; TagInfo tag = PrepareTag(taxonomy.TaxonomyID); // Creates the tag under the taxonomy for which the event was triggered tagInfoProvider.Set(tag); } // Invoked by the asynchronous SetAsync operation public async Task HandleAsync(InfoObjectAfterInsertEvent<TaxonomyInfo> infoObjectEvent, CancellationToken cancellationToken) { TaxonomyInfo taxonomy = infoObjectEvent.InfoObject; TagInfo tag = PrepareTag(taxonomy.TaxonomyID); // Asynchronously creates the tag under the taxonomy for which the event was triggered await tagInfoProvider.SetAsync(tag); } // Prepares a tag object under the specified taxonomy private TagInfo PrepareTag(int taxonomyId) { return new TagInfo { TagTaxonomyID = taxonomyId, TagOrder = 1, TagName = "InitialTag", TagTitle = "Initial tag", TagDescription = "This initial tag was created automatically by an event handler." }; } }
Create a module class in your custom assembly and override the
OnPreInit
method.Register the event handler by calling the
AddInfoObjectEventHandler
extension method forIServiceCollection
.C#using CMS; using CMS.Core; using CMS.DataEngine; using CMS.ContentEngine; [assembly: RegisterModule(typeof(ObjectEventModule))] public class ObjectEventModule : Module { public ObjectEventModule() : base(nameof(ObjectEventModule)) { } // OnPreInit allows you to access IServiceCollection via ModulePreInitParameters protected override void OnPreInit(ModulePreInitParameters parameters) { base.OnPreInit(parameters); // Registers the event handler parameters.Services.AddInfoObjectEventHandler<InfoObjectAfterInsertEvent<TaxonomyInfo>, TaxonomyInitialTagHandler>(); } }
To try out the functionality of the custom event handler, create a new taxonomy:
- Open the Taxonomy application in the Xperience administration.
- Select New taxonomy.
- Enter a Taxonomy name.
- Select Save.
The new taxonomy is created and the registered event handler automatically creates the Initial tag.
Pass state between before and after events
The system allows you to pass custom state information from the Before
handler to the After
handler of an object event. Use the State
property of the corresponding event class, which is available as the parameter of your handler’s Handle
and HandleAsync
methods.
The State
property (EventStateStore
) provides a key-value store, which you can manage using the SetValue<TValue>
and TryGetValue<TValue>
methods. State values stored in Before
handlers are available for retrieval in After
handlers.
public void Handle(InfoObjectBeforeInsertEvent<TaxonomyInfo> infoObjectEvent)
{
// Stores a value under the "TagTitle" key
infoObjectEvent.State.SetValue<string>("TagTitle", "Initial tag");
}
public void Handle(InfoObjectAfterInsertEvent<TaxonomyInfo> infoObjectEvent)
{
// Gets the State value stored under the "TagTitle" key in the before event
string tagTitle;
infoObjectEvent.State.TryGetValue<string>("TagTitle", out tagTitle);
//...
}
Unique State key values
The values of keys stored in the State
property are shared across all handlers registered for a given event class.
- For example, all registered
InfoObjectAfterInsertEvent<UserInfo>
handlers use the same state pool. - Handlers for specific object types have their own separate state. For example,
InfoObjectBeforeInsertEvent<UserInfo>
andInfoObjectBeforeInsertEvent<TaxonomyInfo>
handlers do not share state. - Handlers that target all object types (without a generic parameter) share state with all object-type-specific handlers. For example, state keys added in
InfoObjectBeforeUpdateEvent
handlers are available inInfoObjectAfterUpdateEvent<UserInfo>
handlers.
Make sure you use sufficiently unique keys to avoid overwriting data between different handlers.
Legacy object event handlers
In most cases, we recommend using handlers with async support for object events (Insert, Update, Delete).
However, the system also continues to support the following classes that provide access to legacy object event handling:
ObjectEvents
– events triggered for all object types- <name>Info.TYPEINFO.Events – events triggered only for a specific object type, for example:
UserInfo.TYPEINFO.Events
See the ObjectEvents reference for detailed information.
These events are triggered for both synchronous and async operations with the corresponding objects.