Kentico 8 Technology - Event handlers

   —   
Event handlers have an important role in the customization of Kentico. Events allow you to attach your custom code to the various system actions, and easily add custom functionality throughout it. This article will give you more insight into how we improved event handlers in Kentico 8.
Hi There,

One significant part of the Kentico 8 improvements were the event handlers and their capabilities. This includes not only giving you more control over them, but also providing even more event handlers than before, so you can react to more system events.

The concept of events

Before I start describing what is new, I want you to know some basics in case you haven’t heard about Kentico event handlers yet, or haven’t used them so far.

Kentico event handlers are not that different from .NET events. In fact, they are based on them internally. To provide you more control over them, and make them fully managed from Kentico’s end, we wrapped the events into more sophisticated classes that can be used to efficiently access them.

There are two main types of events:
  • Simple – Represents a specific point in code that is executed. It provides one Execute event to which you can attach your custom code.
  • Advanced – Represents a block of code that is executed, so that you can react before and after the code is executed. It provides two main events Before and After, that you can leverage.
The reason why we use a Before/After naming convention instead of ing/ed suffixes is the way how the handlers are built and presented. In our case, the whole block is handled through a single closely connected process of one event handler, so that these two events go hand in hand.

I don’t want to focus too much on basics in this article, so for more basics, see Kentico documentation: https://docs.kentico.com/display/K8/Handling+global+events
Now, I would like to focus more on how to create your own events, and what functionality they can offer out-of-the-box.

Providing your own events

As I said, there are two types of events. For simplicity’s sake, I will use the same event arguments for both and define both types of handlers in my code:

using CMS.Base; /// <summary> /// Event arguments /// </summary> public class MyEventArgs : CMSEventArgs { /// <summary> /// Event parameter /// </summary> public string MyParameter { get; set; } } /// <summary> /// Simple handler type /// </summary> public class MySimpleHandler : SimpleHandler<MySimpleHandler, MyEventArgs> { } /// <summary> /// Advanced handler type /// </summary> public class MyAdvancedHandler : AdvancedHandler<MyAdvancedHandler, MyEventArgs> { } /// <summary> /// Global events /// </summary> public static class MyEvents { /// <summary> /// Event for a simple action /// </summary> public static MySimpleHandler SimpleAction = new MySimpleHandler { Name = "MyEvents.SimpleAction" }; /// <summary> /// Event for an advanced action /// </summary> public static MyAdvancedHandler AdvancedAction = new MyAdvancedHandler { Name = "MyEvents.AdvancedAction" }; }


I have created four classes in this case:
  • MyEventArgs – Class that holds arguments specific for the handlers.
  • MySimpleHandler – Class that defines the specific handler type for my simple event.
  • MyAdvancedHandler – Class that defines the specific handler type for my advanced event.
  • MyEvents – Static class that provides the global events as entry points through its static members.
You may notice that I am giving names to the events. This is optional, but if you do this, you will be able to identify them in the debug, so I recommend that you do this as well.

Raising events from your code

Having these, we can now raise the events in our code in the following way. You define the event arguments for both:

var e = new MyEventArgs { MyParameter = "Hello" };

 
Then for a simple event, you just call this one line of code:
 
// Raise simple event MyEvents.SimpleAction.StartEvent(e);

 
And for an advanced event, you wrap your code block into this construct:       

// Raise advanced event using (var h = MyEvents.AdvancedAction.StartEvent(e)) { if (h.CanContinue()) { // Executing block of code } h.FinishEvent(); }


You can see that, depending on how they are being used, both types of events can differ. That is because the simple event is one-time only, while the advanced event surrounds the block of code with before and after.

Attaching event handlers

Now, let me show you how to attach handler methods to these events. Particular examples demonstrate at which point in the previous example a particular method is being executed:

public void Attach() { MyEvents.SimpleAction.Execute += SimpleAction_Execute; MyEvents.AdvancedAction.Before += AdvancedAction_Before; MyEvents.AdvancedAction.After += AdvancedAction_After; } void SimpleAction_Execute(object sender, MyEventArgs e) { // Fires at MyEvents.SimpleAction.StartEvent(e); } void AdvancedAction_Before(object sender, MyEventArgs e) { // Fires at MyEvents.AdvancedAction.StartEvent(e) } void AdvancedAction_After(object sender, MyEventArgs e) { // Fires at h.FinishEvent(); }


You can work with all of the system events in the same way. All of them are either simple or advanced events. For the full list of system events, see https://docs.kentico.com/display/K8/Reference+-+Global+system+events

Attaching events in Kentico application

Now it’s time to answer one important question: “What is the right time to attach a global event in Kentico application?”

The general answer to this question is always “Any time before the first event of this type occurs”.

To be more specific, the right time to attach events is basically during or after the start or initialization of the application.

Later on in another article, I will show you how you can do that from a module initialization even from the class library. For the purpose of this article, leveraging the concept of ModuleLoader in App_Code as described in https://docs.kentico.com/display/K8/Handling+global+events is enough. Notice that the Init method described in that documentation is executed one time within the application initialization.

Now that you know the basic introduction to Kentico event handlers, let me show you what is new in Kentico 8.

Handlers debug

First of all, I would like to mention the debug functionality for event handlers. As you probably know, Kentico comes with various debugs so that you can monitor and trace behavior of a particular processes within the system.

Event handlers debug can give you the big picture about a particular request life-cycle, to simplify your decision as to which event you should use for you code:

Debug.png

As you can see in the screenshot, you can also see the order of any particular attached actions so that you know what else is being executed in the given context. The debug shows all particular actions in a hierarchy view.

Advanced handler posibilities

The majority of event handler improvements in Kentico 8 is related to advanced handlers. Imagine a situation, where you need to gain control over the whole block of code surrounded by the handler, not just the individual before/after, such as this:

using (var fs = new FileStream("somePath", FileMode.Create)) { // Work with the file stream fs.Write(...); // -- Original system action -- // Further work with the file stream fs.Write(...); } // Dispose the file stream


In the traditional way of using individual and independent Before/After events, you have to take care to not only pass the file stream object to the After handler, but also make sure, that fs.Dispose() is named properly when an error occurs.

Luckily, Kentico 8 has several concepts elaborated for these specific situations.

Note that I will use the previously defined AdvancedAction in the following examples to show you the outcome of these particular features. In all scenarios, we will leverage methods provided by the CMSEventArgs class that serves as a base for your event arguments.

As a base for each of the following examples, we will use this code:

using (var h = MyEvents.AdvancedAction.StartEvent(e)) { if (h.CanContinue()) { // -- Original block of code -- } h.FinishEvent(); } void AdvancedAction_Before(object sender, MyEventArgs e) { // Before action } void AdvancedAction_After(object sender, MyEventArgs e) { // After action }


That executes as the following:

// Before action // -- Original block of code -- // After action


Now let me show you what Kentico 8 is capable of.

A using statement with automatic dispose

If we modify the previous code to use the Using method of the event arguments such as this:

void AdvancedAction_Before(object sender, MyEventArgs e) { // Before action var fs = new FileStream("somePath"); e.Using(fs); // Work with the file stream fs.Write(...); }

 
It models the final execution of the code as the following:

// Before action using (var fs = new FileStream("somePath", FileMode.Create)) { // Work with the file stream fs.Write(...); // -- Original block of code -- // After action }


Calling the Using method basically wraps the rest of the handler block simulating the using statement. It automatically handles the disposal of the object for you even in error scenarios.

You can leverage this to easily apply context specific behavior to the whole event using CMSActionContext or other similar concepts. The following example disables the creating of search tasks within the whole operation:

void AdvancedAction_Before(object sender, MyEventArgs e) { // Before action e.Using(new CMSActionContext { CreateSearchTask = false }); // Before action in action context }


And acts as the code would execute as the following:

// Before action using (new CMSActionContext { CreateSearchTask = false }) { // Before action in action context // -- Original system action -- // After action }


In a similar manner, you can apply additional closely-bound logic that wraps the whole event.

Passing variables from Before to After

The previous code shows how to leverage a using statement with automatic dispose, but it still doesn’t cover the whole scenario with the file stream. We are still missing the After part where we want to work with the stream.

To do that, we are not going to put that code to the AdvancedAction_After method as the variable fs is not present there. Instead, we are going to leverage the CallWhenFinished method of the event arguments and use lambda with a closure to pass in the variable. I have included:

void AdvancedAction_Before(object sender, MyEventArgs e) { var fs = new FileStream("somePath"); e.Using(fs); // Before action using file stream fs.Write(...); e.CallWhenFinished(() => { // Further work with the file stream fs.Write(...); }); }


The resulting code will now execute as it was written in the following way:

// Before action using (var fs = new FileStream("somePath", FileMode.Create)) { // Work with the file stream fs.Write(...); // -- Original block of code -- // After action // Further work with the file stream fs.Write(...); }


And the whole scenario with file stream is covered.

An alternative way of doing the same would be to leverage RequestStockHelper or Context class to pass the variable (which was what we advised with earlier versions of Kentico), but that would require also additional control over potential redundancy of the handlers. That’s why we introduced this model.

You could argue that you can leverage the event arguments themselves to pass the object through the event. But in reality, the author of the event and the person that attaches the event handler are typically different people from different companies, so no one in this situation can cover all of the possible scenarios in the event arguments. If it would all be found within a single organization entity and product. There would be no need to make an event, and they could write it all into the code block itself, right?

If you’ve ever tried to write this code yourselves, you might have noticed that there is also a method called CallOnDispose that you can leverage to call additional actions; these will execute at the very end of the handler block. Use this when you need to execute some action as it was placed in the try .. finally block of code, and execute regardless of whether the operation succeeds or not. This is handy especially in scenarios where you need to reset some temporary property to its previous value.

I would actually like to add one more note to this topic:

If you leverage the events in ObjectEvents, or, more specifically in ObjectTypeInfo, especially the Update event, you may want to execute some actions in the After part of the event based on the particular changed fields of the object. As the status of the object is already reset in the After part of the event (because it was already saved to the database, therefore no field is considered changed), you need to leverage this concept to pass the detected change from Before to After in order to make it work as you expect. Let me show you a quick example to clarify how to do that:

RoleInfo.TYPEINFO.Events.Update.Before += Role_Update_Before; void Role_Update_Before(object sender, ObjectEventArgs e) { var role = (RoleInfo)e.Object; var roleNameChanged = role.ItemChanged("RoleName"); var originalRoleName = role.GetOriginalValue("RoleName"); if (roleNameChanged) { e.CallWhenFinished(() => { // Do some action with the role and original data TransferRoleData(originalRoleName, role.RoleName); }); } }


In this example, we detect a change to the role name, and make some additional action to reflect it in some related data.

Using locks

Similar to Using method, Kentico 8 event handlers support the same concept for using locks through the Lock method of the event arguments. Here is a sample code for that:

static object lockObject = new object(); void AdvancedAction_Before(object sender, MyEventArgs e) { // Before action e.Lock(lockObject); // Locked before action }


This code executes in the context of the original scenario as it was written this way:

// Before action lock (lockObject) { // Locked before action // -- Original block of code -- // After action }

Using transactions

Now that you know that you can use Using to wrap the code to a using statement, I guess you can imagine that you could use the following call to simply wrap the handler to a transaction:

var tr = new CMSTransactionScope(); e.Using(tr); e.CallWhenFinished(tr.Commit);


To simplify this for you, we have provided the method called UseTransaction within the event arguments. So, if you use the following code:

void AdvancedAction_Before(object sender, MyEventArgs e) { // Before action e.UseTransaction(); // Before action within transaction }


It results in the following order of execution and actions:

// Before action using (var tr = new CMSTransactionScope()) { // Before action within transaction // -- Original block of code -- // After action tr.Commit(); }


As always, any “code block” that you start like this will last the entire time until the very end of the handler block.

Additional considerations

I have shown you a lot of the handler’s power, but don’t use these things just because you like them.

All of these aspects that “wrap the code” more or less lock the context of resources for other threads, so use them with care, and try to avoid them in situations where you need high performance.

You know it, “With great power comes great responsibility”…

A rule of thumb: Always make locks and transactions as short as possible, not only code-wise, but especially time-wise.
The only completely safe method from the ones presented is CallWhenFinished, as it doesn’t lock the context in any way.
Using is safe only in cases when it doesn’t use a shared resource.

Still, be careful with what you are executing within the event handler calls to make sure that you are not compromising the overall application performance. If possible, raise additional actions as asynchronous processes.

Recursion control

There is one more important topic to discuss in this article. The short version of it is:

“The Kentico handlers now have automatic recursion control”

But let me give you some more insight into that … It all started as a UserVoice conversation, and quickly made it through as it seemed logical and was also backed by other voices that gave us the same feedback through our technical support.

Imagine the following scenario: you need to update the particular object in its Update.After event, essentially set some of its property and save it. If you do that in Kentico 7 and don’t handle recursion in some way, it ends up in an infinite loop. But sometimes, the update may just be an automatic consequence of some other action that you cannot control, which makes the situation even harder.

Before I get to handlers, let me show you one general concept that Kentico 8 offers. It is called recursion control, and you can use it this way:

string key = ... // Create a unique key that identifies the operation using (var rc = new RecursionControl(key)) { if (rc.Continue) { // Potentially cycling recursive operation } }


The code above simply ensures that the same thread doesn’t enter the same code block twice under the same key. This is essentially the same concept that we recommended with earlier versions, which was: “Put a flag under unique key to a RequestStockHelper, and detect the recursion that way.” The way we provide right now is basically the same concept, just wrapped into a nice and clear API.

Now let me get to the fun part – the handlers. If you want the recursion control for your handler, all you need to do is to make it implement the IRecursionControlHandler<TArgs> interface, and its method GetRecursionKey(TArgs). This provides the unique recursion key to the RecursionControl class used within the internal handler code; the recursion is then handled automatically.

Note that the recursion is controlled individually per each method attached to the handler, so even if your particular After event handler is recursive, it only skips it when it detects recursion, and not the rest of the attached methods. That way, the system doesn’t skip any important actions related to your additional change, such as logging of staging tasks, to event log, or other actions attached through the event handlers.

In the case of default handlers for Kentico 8, the recursion control is automatically included in the handlers Update, Insert, Delete in all general objects, documents, custom tables and online forms, as well as these events of a particular object type info. The key used for automatic recursion control in Kentico handlers consists of:
  • Object type, Object ID – So that more objects saved at the same time don‘t collide
  • Event name (as present in debug) – So that different events don’t collide
  • Name of the operation (Before / After) – So that these don’t collide
  • Order of the attached method – To control recursion individually per attached method
To show you a specific example, if I execute the following code in Kentico 8:

CountryInfo.TYPEINFO.Events.Update.After += Country_Update_After; private void Country_Update_After(object sender, ObjectEventArgs e) { var country = e.Object as CountryInfo; country.CountryDisplayName += " A"; country.Update(); }


It executes the Country_Update_After method only once when the country is updated.

With your own handlers, you can use any key you like that uniquely identifies the operation. The event name, operation, and order is added for you automatically.

Wrap up

I have shown you all the goodies that Kentico 8 event handlers have to offer. This includes support for:
  • Usings
  • Locks
  • Transactions
  • Passing values from Before to After parts of the event.
  • Automatic recursion control
By this time, you should be able to leverage them to their full potential, including making your own, and understanding how they work.

Be careful about using particular features too much, as they may have unwanted performance implications…

See you next time!
Share this article on   LinkedIn

Martin Hejtmanek

Hi, I am the CTO of Kentico and I will be constantly providing you the information about current development process and other interesting technical things you might want to know about Kentico.

Comments