Using Chain of Command in X++
The Chain of Command pattern has been available in Microsoft Dynamics 365 for a few year (since Platform Update 9), and we’re still seeing miscommunications around the topic. In this week’s blog, we will answer: What is it? How do I use it? When would I use it? What does it replace?
What is the Chain of Command Pattern in X++?
In Microsoft Dynamics 365, Chain of Command (CoC) enables customization and extension of base Microsoft code. Microsoft’s base objects and code cannot be changed directly in D365, but CoC allows for the application’s capabilities to be extended.
The easiest way to explain chain of command is that next() is similarly used as super(). Anything before the next() call can be considered a pre-event handler and anything after it is considered a post-event handler.
Please keep in mind that this is not creating a child class by the keyword extends. It is, however, allowing the developer to add business logic before or after existing method calls. It is called a chain because of the chain of events that occurs. Consider using chain of command on the update method for theSalesTable table. The chain would execute as follows:
salesTable_Extension.update() before next() -> salesTable.update() -> salesTable_Extension.update() after next()
Hidden Treasures of Advanced Warehousing
Make the most of Microsoft Dynamics 365’s Advanced Warehousing module and gain insight into the hidden treasures to unlock value.
Using X++ Chain of Command Examples
This can effectively eliminate the need to have two event handlers. For example, separate pre and post event handlers can be replaced by a single chain of command extension. This can be used for both protected and public methods.
The Fine Print
Using CoC for D365 requires a few things:
- Decorating the class with [extensionOf(
)]
- You can extend tables, forms, and classes. In today’s example, we show you extending a table. Nothing really changes in extending forms and classes except what the ‘this’ keyword is in reference to.
- You must use intrinsic functions tableStr(), formStr(), or classStr() to identify the object you’re extending.
- Making your class final
- Your method signature must match the method you’re extending
- Optional parameters are not supported.
- If the method you’re extending has optional parameters, leave off the defaulting.
- The class name must end with _Extension
- This is by design for the compiler to recognize classes that are using chain of command for faster compile times. It will throw a compiler error if this is not present.
As of the publishing date of this post, Microsoft states that the order in which extensions are called is to be considered random. What does this mean? If you have several extensions on the same method, then you cannot depend on the order of your call being first or last.
Recent Updates
In Dynamics 365 X++ development, a replaceable attribute is used to mark methods that can be extended using the Chain of Command pattern without the requirement to call the next method. This allows developers to completely override the logic of the base method if needed.
How it works:
- Replaceable Methods: When a method is marked with the [Replaceable] attribute, it indicates that the method can be extended, and the next call (which invokes the original method) is optional. This means you can choose to skip the original method’s execution based on your custom logic.
- Conditional Execution: While you can skip the next call, it’s generally recommended to do so conditionally. This ensures that the base functionality is preserved unless specific conditions are met that require overriding it completely.
Example of pre and post event handler:
class salesTableEventHandler
{
const static str deliveryArgName = 'deliveryDate';
[PreHandlerFor(tableStr(SalesTable), tableMethodStr(SalesTable, update))]
public static void SalesTable_Pre_update(XppPrePostArgs _args)
{
SalesTable salesTable = _args.getThis();
// store the pre update value for later
_args.addArg(salesTableEventHandler::deliveryArgName, salesTable.DeliveryDate);
}
[PostHandlerFor(tableStr(SalesTable), tableMethodStr(SalesTable, update))]
public static void SalesTable_Post_update(XppPrePostArgs _args)
{
// get the stored value that we placed in args up above
SalesShippingDate origDeliveryDate = _args.getArg(salesTableEventHandler::deliveryArgName);
SalesTable salesTable = _args.getThis();
if (salesTable.DeliveryDate != origDeliveryDate)
{
warning("The delivery date has been updated, make sure the new date is attainable.");
}
}
}
How to replace event handlers using chain of command:
[ExtensionOf(tableStr(SalesTable))]
final class salesTable_Extension
{
// extending an existing method, so method signature matches
public void update()
{
SalesShippingDate origDeliveryDate = this.DeliveryDate;
// before the next is pre-update
next update();
// after the next is post-update
if (this.DeliveryDate != origDeliveryDate)
{
warning("The delivery date has been updated, make sure the new date is attainable.");
}
}
}
In this blog, we have shown what chain of command is, how to use it, and what it effectively replaces in your programming needs to fit the needs of the business.
enVista Does it Best
enVista is dedicated to helping the Microsoft community by continuously sharing our in-depth knowledge of X++ and Microsoft Dynamics 365 development. If you are struggling with Dynamics AX or D365 development, contact us, and we will connect you with one of our in-house experts.
Read our other topics in our D365 Developer Series: