Create pre-event and post-event handler classes

Completed

You can use event handler classes for pre-handlers and post-handlers to extend an application, like how you’d use Chain of Command (CoC) for method wrapping.

Although pre-event and post-event handler classes are a valid extensibility pattern, they shouldn’t be your first choice in most scenarios. Because event handlers are subscribed indirectly through attributes instead of by wrapping the original method, some issues are only detected at runtime. For example, if the underlying method signature or behavior changes, your handler might still compile but fail or behave unexpectedly when it runs.

For example, if a base method signature changes from:

insert(boolean _validate)

to:

insert(boolean _validate, boolean _skipEvents)

an existing event handler may still compile, but can fail or behave unexpectedly when invoked, depending on the handler type and subscription pattern.

For extending public or protected methods, prefer Chain of Command (CoC) (method wrapping) as the default and more robust approach. CoC enforces the method signature at compile time, keeps the extension tightly coupled to the base method, and makes the execution order explicit through the next call.

Use pre- and post-event handlers only when:

  • Chain of Command is not available (for example, the method is private, final, or marked with Wrappable(false)).

  • You need to react to framework or platform events that only support event handlers (such as table, form, or delegate-based events).

Example: Extending a method using Chain of Command

[ExtensionOf(classStr(BusinessLogic1))]
final class BusinessLogic1_Extension
{
    str doSomething(int arg)
    {
        var s = next doSomething(arg + 4);
        return s;
    }
}

Important:

  • Chain of Command requires the use of the next keyword so that all methods in the chain contribute to the final behavior.

  • The method signature must not be changed in a CoC extension.

  • Skipping the next call is only allowed for methods explicitly marked as Replaceable, and should be done with caution.

An event handler class is another way to extend an application, and you use it as you would other event handler classes. You can use pre-handlers and post-handlers in a class where the original method has a pre-handler that runs before the original method and a post-handler that you would implement afterward. Handlers surround the original method. Additionally, you can use attributes to prevent or allow extending pre-handlers and post-handlers.

Pre-event and post-event classes

To add a pre-event and post-event class, create a new event handler class. You can add a class to a project from the Solution Explorer window by right clicking the project or activating the context menu and then selecting Add, New item, Code, Class or Add Class.

Enter a descriptive name for the pre-event and post-event class, such as CustTableTableEventHandler. It’s best practice to use the object name as a starting point, such as CustTable, and then add the object type, such as Table, because CustTable could be a table or a form. Then, you can append it with EventHandler. For example, you could have two event handler classes that are named CustTableTableEventHandler and CustTableFormEventHandler.

To add a pre-event or post-event, make sure that the method that’s in the event handler class is discoverable. On the method, right-click or activate the context menu, select Copy event handler method, and then select Pre-event handler, as the following image depicts.

Visual Studio screenshot showing how to copy event handler method from
and then select Pre-event handler.

After you select the pre-event handler, go to the event handler class, and then paste the method. Return to the method, select the post-event handler, and then paste it in the event handler class.

Two methods for pre-events and post-events should display, and the original method is called between these two methods (super call), as the following code sample illustrates:

internal final class CustTableTableEventHandler
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="args"></param>
    [PreHandlerFor(tableStr(CustTable), tableMethodStr(CustTable, insert))]
    public static void CustTable_Pre_delete(XppPrePostArgs args)
    {
        CustTable custTable = args.getThis() as CustTable;

        // do something
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="args"></param>
    [PostHandlerFor(tableStr(CustTable), tableMethodStr(CustTable, insert))]
    public static void CustTable_Post_delete(XppPrePostArgs args)
    {
        CustTable custTable = args.getThis() as CustTable;

        // do something
    }
}

When to use pre-event and post-event handlers

Use pre-event and post-event handlers when:

  • The target method is not wrappable or CoC is explicitly disabled (for example, the method is private, final, or marked with [Wrappable(false)]).

  • You need to react to a framework or platform event that only exposes pre/post handler hooks and does not support CoC.

  • You are responding to an event rather than extending or altering the core implementation of a specific method.

Avoid using pre-event and post-event handlers as your default extensibility pattern because:

  • Handlers are loosely coupled to the original method through attributes, so some problems are only detected at runtime instead of at compile time.

  • Multiple handlers can subscribe to the same event, which can make the execution order and resulting behavior harder to reason about and test.

If a method is wrappable and designed for extension, use CoC and method wrapping instead. CoC provides a richer and more concise extension mechanism and keeps your extension code close to the original method implementation.

Attributes

You can add attributes to a method to prevent people from using pre-handlers and post-handlers and other extensions. The three types of attributes that you can add are hookable, wrappable, and replaceable.

Hookable attributes complete the following actions:

  • [Hookable(false)] - Overrides the behavior so that it’s not possible to create pre-events and post-events, which means that the method isn’t wrappable.
  • [Hookable(true)] - Overrides the behavior so that it’s possible to create pre-events and post-events.

Wrappable attributes complete the following actions:

  • [Wrappable(false)] - Overrides the default capability for (non-final) public and protected methods to be wrapped, so it’s no longer possible to wrap the method. In other words, the method isn’t wrappable.
  • [Wrappable(true)] - Overrides the behavior of not being able to wrap methods that are marked as final, so it is possible to wrap the method. In other words, the method becomes wrappable.

Replaceable attributes complete the following actions:

  • [Replaceable (false)] - Overrides the behavior so that Chain of Command (CoC) must have an unconditional next call.
  • [Replaceable (true)] Overrides the behavior so that it’s possible to have a CoC extension, but they don’t need to unconditionally call next.

The replaceable method must also be wrappable.

Important

Extensibility attributes determine how and whether a method can be extended.

When a method is marked with [Wrappable(false)], it cannot be extended using Chain of Command (CoC).
When a method or delegate is marked with [Hookable(false)], it cannot expose hook points for event or delegate-based extensions.

These attributes directly affect the extensibility options available to other models. Applying them should be a deliberate decision, because changing or adding these attributes later is a breaking change that can invalidate existing extensions or force developers to fall back to less explicit and less predictable extensibility patterns.

The following code example shows that the method is protected to have pre-events and post-events and CoC by using [Hookable(false), Wrappable(false)], which means that no extensibility is available.

    /// <summary>
    /// Calculates and sets the <c>AmountCur</c> field using the <c>Quantity</c> and <c>UnitPrice</c>
    /// values.
    /// </summary>
    /// <param name="_qty">
    /// Item quantity on the line.
    /// </param>
    /// <returns>
    /// The <c>AmountCur</c> value.
    /// </returns>
    [Hookable(false), Wrappable(false)]
    public AmountCur calcLineAmount(Qty _qty = this.Quantity)
    {
        return this.salesPurchLineInterface().calcLineAmount(_qty);
    }

When you’re using attributes for methods, make sure that you’re aware of extensibility and potentially breaking it.

How event handlers relate to delegates

Pre- and post-event handlers and delegates are both extensibility mechanisms:

  • Pre/post event handlers run before or after a method and are attached by using attributes like [PreHandlerFor] and [PostHandlerFor].

  • Delegates are defined in the base application code and called from the middle of a method. Extensions subscribe to those delegates to inject additional behavior.

Delegates are useful when the application exposes a delegate as the official extensibility point. However, the best practice is still to favor CoC and method wrapping whenever possible, because they provide a richer and more robust extension mechanism than either pre/post handlers or delegates.

To learn more about delegates and how to subscribe to them, see Implement delegates