Class extension via method wrapping and Chain of Command (CoC)
Song Nghia - Technical Consultant
The functionality for class extension, or class augmentation, has been improved in Microsoft Dynamics 365 for Finance and Operations. You can now wrap logic around methods that are defined in the base class that you're augmenting. You can extend the logic of public and protected methods without having to use event handlers. When you wrap a method, you can also access public and protected methods, and variables of the base class. In this way, you can start transactions and easily manage state variables that are associated with your class.
For example, a model contains the following code.
You can now augment the functionality of the DoSomething method inside an extension class by reusing the same method name. An extension class must belong to a package that references the model where the augmented class is defined.
In this example, the wrapper around DoSomething and the required use of the next keyword create a Chain of Command (CoC) for the method. CoC is a design pattern where a request is handled by a series of receivers. The pattern supports loose coupling of the sender and the receivers.
We now run the following code.
When this code is run, the system finds any method that wraps the DoSomething method. The system randomly runs one of these methods, such as the DoSomething method of the BusinessLogic1_Extension class. When the call to the next DoSomething method occurs, the system randomly picks another method in the CoC. If no more wrapped methods exist, the system calls the original implementation.
The functionality that is described in this topic (CoC and access to protected methods and variables) is available in Platform update 9. However, the class that is being augmented must also be compiled on Platform update 9 or later. As of August 2017, all current releases of the applications for Finance and Operations have been compiled on Platform update 8 or earlier. Therefore, to wrap a method that is defined in a base package (such as Application Suite), you must recompile that base package on Platform update 9 or later. As an example: If you create your own extension model that is augmenting a class that exists in the Application Suite model, and if you are using CoC or accessing protected methods/variables, you will need to build both Application Suite and your extension model. You will also need to create a deployable package that includes both models in order to deploy this functionality on a runtime environment. This is a temporary situation until the next release of the Dynamics 365 for Finance and Operations application.
The following sections give more details about the capabilities of method wrapping and CoC.
Wrapping public and protected methods
Protected or public methods of classes, tables, or forms can be wrapped by using an extension class that augments that class, table, or form. The wrapper method must have the same signature as the base method.
· When you augment form classes, only root-level methods can be wrapped. You can't wrap methods that are defined in nested classes.
· Only methods that are defined in regular classes can be wrapped. Methods that are defined in extension classes can't be wrapped by augmenting the extension classes.
What about default parameters?
Methods that have default parameters can be wrapped by extension classes. However, the method signature in the wrapper method must not include the default value of the parameter.
For example, the following simple class has a method that has a default parameter.
In this case, the wrapper method must resemble the following example.
In the aPerson_Extension extension class, notice that the salute method doesn't include the default value of the message parameter.
Wrapping instance and static methods
Instance and static methods can be wrapped by extension classes. If a static method is the target that will be wrapped, the method in the extension must be qualified by using the statickeyword.
For example, we have the following A class.
In this case, the wrapper method must resemble the following example.
The ability to wrap static methods doesn't apply to forms. In X++, a form class isn't a new class, and can't be instantiated or referenced as a normal class. Static methods in forms don't have any semantics.
Wrapper methods must always call next
Wrapper methods in an extension class must always call next, so that the next method in the chain and, finally, the original implementation are always called. This restriction helps guarantee that every method in the chain contributes to the result.
In the current implementation of this restriction, the call to next must be in the first-level statements in the method body.
Here are some important rules:
· Calls to next can't be done conditionally inside an if statement.
· Calls to next can't be done in while, do-while, or for loop statements.
· A next statement can't be preceded by a return statement.
· Because logical expressions are optimized, calls to next can't occur in logical expressions. At runtime, the execution of the complete expression isn't guaranteed.
The author of the original implementation of a method can explicitly allow wrapper methods to skip calling next. If the method you are wrapping is tagged with the [Replaceable] attribute, an extension class can wrap this method without calling the nextkeyword. Replaceable methods are methods that implement logic that can safely be "replaced" by custom implementation. This functionality is available with the release of Platform update 11.
Wrapping a base method in an extension of a derived class
The following example shows how to wrap a base method in an extension of a derived class. For this example, the following class hierarchy is used.
Therefore, there is one base class, A. Two classes, B and C, are derived from A. We will augment or create an extension class of one of the derived classes (in this case, B), as shown here.
Although the aB_Extension class is an extension of B, and B doesn't have a method definition for the salute method, you can wrap the salute method that is defined in the base class, A. Therefore, only instances of the B class will include the wrapping of the salute method. Instances of the A and C classes will never call the wrapper method that is defined in the extension of the B class.
This behavior becomes clearer if we implement a method that uses these three classes.
For calls to a.salute(“Hi”) and c.salute(“Hi”), the Infolog shows only the message “Hi.” However, when b.salute(“Hi”) is called, the Infolog shows “Hi” followed by “B extension.”
By using this mechanism, you can wrap the original method only for specific derived classes.
Accessing protected members from extension classes
As of Platform update 9, you can access protected members from extension classes. These protected members include fields and methods. Note that this support isn't specific to wrapping methods but applies all the methods in the class extension. Therefore, class extensions are more powerful than they were before.
The Hookable attribute
If a method is explicitly marked as [Hookable(false)], the method can't be wrapped in an extension class. In the following example, anyMethod can't be wrapped in a class that augments anyClass1.
Final methods and the Wrappable attribute
Public and protected methods that are marked as final can't be wrapped in extension classes. You can override this restriction by using the Wrappable attribute and setting the attribute parameter to true ([Wrappable(true)]). Similarly, to override the default capability for (non-final) public or protected methods, you can mark those methods as non-wrappable ([Wrappable(false)]).
In the following example, the doSomething method is explicitly marked as non-wrappable, even though it's a public method. The doSomethingElse method is explicitly marked as wrappable, even though it's a final method.
Extensions of form-nested concepts such as data sources, data fields, and controls
In order to implement CoC methods for form-nested concepts, such as data sources, data fields, and controls, an extension class is required for each nested concept.
Form data sources
In this example, FormToExtend is the form, DataSource1 is a valid existing data source in the form, and init and validateWrite are methods that can be wrapped in the data source.
Form data fields
In this example, a data field is extended. FormToExtend is the form, DataSource1 is a data source in the form, Field1 is a field in the data source, and validate is one of many methods that can be wrapped in this nested concept.
In this example, FormToExtend is the form, Button1 is the button control in the form, and clicked is a method that can be wrapped on the button control.
Requirements and considerations when you write CoC methods on extensions for form-nested concepts
· Like other CoC methods, these methods must always call next to invoke the next method in the chain, so that the chain can go all the way to the kernel or native implementation in the runtime behavior. The call to next is equivalent to a call to super()from the form itself to help guarantee that the base behavior in the runtime is always run as expected.
· Currently, the X++ editor in Microsoft Visual Studio doesn't support discovery of methods that can be wrapped. Therefore, you must refer to the system documentation for each nested concept to identify the correct method to wrap and its exact signature.
· You cannot add CoC to wrap methods that aren't defined in the original base behavior of the nested control type. For example, you can't add methodInButton1 CoC on an extension. However, from the control extension, you can make a call into this method if the method has been defined as public or protected. Here is an example where the Button1 control is defined in the FormToExtend form in such a way that it has the methodInButton1 method.
· You do not have to recompile the module where the original form is defined to support CoC methods on nested concepts on that form from an extension. For example, if the FormToExtend form from the previous examples is in the ApplicationSuite module, you don't have to recompile ApplicationSuite to extend it with CoC for nested concepts on that form from a different module.
Restrictions on wrapper methods
The following sections describe restrictions on the use of CoC and method wrapping.
Kernel methods can't be wrapped
Kernel classes aren't X++ classes. Instead, they are classes that are defined in the kernel of the Microsoft Dynamics 365 Unified Operations platform. Even though extension classes are supported for kernel classes, method wrapping isn't supported for methods of kernel classes. In other words, if you want to wrap a method, the base method must be an X++ method.
X++ classes that are compiled by using Platform update 8 or earlier
The method wrapping feature requires specific functionality that is emitted by an X++ compiler that is part of Platform update 9 or later. Methods that are compiled by using earlier versions don't have the infrastructure to support this feature.
Nested class methods in forms can be wrapped in Platform update 16 or later
The ability to wrap methods in nested classes by using class extensions was added in Platform update 16. The concept of nested classes in X++ applies to forms for overriding data source methods and form control methods.
For the features that are described in this topic, the Microsoft Visual Studio X++ editor doesn't yet offer complete support for cross-references and Microsoft IntelliSense. We plan to make complete support available in Dynamics 365 for Finance and Operations Platform update 10.