UA-44032151-3 page contents

Embrace the extensions mindset with Dynamics 365 for Finance and Operations


A couple of weeks ago, we launched the last platform update 10 (August 2017) for Dynamics 365 for Finance and Operations, Enterprise Edition and, as in almost every release, there were changes regarding the Application Extensibility Plans.

A lot of effort is being invested in the journey to a non-intrusive way to extend the application, and it needs to be understood as a critical change in the way we think about customizations. More information about future plans can be found in the Dynamics Roadmap (Filter - Application: Dynamics 365 for Finance and Operations; Area: Extensibility).

But sometimes it's hard to change our mindset just by looking at these out-of-context upgrade notes, so I want to show how we can leverage some of the latest improvements to the X++ platform by looking at some real-life examples.

Keep in mind the basic principle: we want to avoid intrusive changes.

 

Scenario 1: Create a number sequence in a standard module

This is a common scenario and the good news is that it has barely changed from Dynamics AX 2012. In our example, we will add a new number sequence to the Accounts Receivables module (Customers) to provide new values for the custom MSVRMInsuranceProviderId EDT. To accomplish that, we need to make some small changes (plenty of examples of AX 2012 code available out there):

  • Add a new code block to the loadModule in the NumberSeqModuleXXX class of our module. I.e.: NumberSeqModuleCust.
  • Add a new static method to the Parameters table of the module to get the number sequence reference, CustParameters in my example. This is not mandatory, though a best practice.

One of my favorite new features in X++ is the Chain of Command (or method wrapping) pattern. It was added in Platform Update 9 (July 2017) and is the key to avoid over-layering in a good number of common scenarios, like ours. It was nicely explained in a video published by Michael F. Pontoppidan on his blog. I encourage you to take a look, it's worth the 10 minutes it takes.

Let's create a new class in an extension model with the following code:

[ExtensionOf(classStr(NumberSeqModuleCustomer))]
final class NumberSeqModuleCustMSVRM_Extension
{
    protected void loadModule()
    {
        NumberSeqDatatype datatype = NumberSeqDatatype::construct();

        next loadModule(); // Execute the augmented original class method

        /* Insurance Provider Id */
        datatype.parmDatatypeId(extendedTypeNum(MSVRMInsuranceProviderId));
        datatype.parmReferenceHelp(literalStr('Provider ID'));
        datatype.parmWizardIsContinuous(false);
        datatype.parmWizardIsManual(NoYes::No);
        datatype.parmWizardIsChangeDownAllowed(NoYes::No);
        datatype.parmWizardIsChangeUpAllowed(NoYes::No);
        datatype.parmSortField(1);
        
        datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);
        this.create(datatype);
    }
}

This is a regular class extension like the ones we are working with since the beginnings of Dynamics 365. The most important parts of the code are marked in bold:

  • _Extension suffix in the class name. This is mandatory as per .NET standards.
  • ExtensionOf attribute tells the compiler which class we are extending.
  • next call. This is the new thing I wanted to show. It allows us to call the same method in the class we are extending from. It can be confused with a super() call, but it's not the same as we don't have any inheritance here neither we know how many extensions this same class may have implemented in different modules. The latter is important as -same with event handlers- we cannot count on the execution order here.

This way we can add (or we can "chain" it, according to the chain-of-command terminology) some code to the existing method and this is enough to satisfy our requirements for this scenario. With just this small extension class, we have a new number sequence in the Accounts receivable parameters form without any over-layering:

Next step is to add the static method that will return the number sequence reference. It is kind of a Best Practice (BP) to add this method to the module's parameters table (CustParameters in my example). In Dynamics AX 2012 there was an accepted exception for this BP where sometimes these methods were created in a custom table (like the parameters table in our own module) to avoid over-layering a standard table just for this small change. Not a problem anymore.

We can create a table extension class to add the static method to the standard table without over-layering:

[ExtensionOf(tableStr(CustParameters))]
final class CustParametersMSVRM_Extension
{
    client server static NumberSequenceReference numRefMSVRMInsuranceProviderId()
    {
        return NumberSeqReference::findReference(extendedTypeNum(MSVRMInsuranceProviderId));
    }
}

Of course, as extensions are strongly typed augmentations of classes, we have IntelliSense over the new methods, as they are in fact part of the final version of the class (called: effective class):

 

Scenario 2: Create a number sequence in a new module

Now just imagine you want to create the same number sequence as in the previous scenario but by creating your own module. Required steps are well known:

  • Add a new element to the NumberSeqModule base enum.
  • Create a NumberSeqModule* class for our module.
  • Let the system know our module exists.

To add a new element to the NumberSeqModule Base Enum we can use an extension (a huge number of new base enums were made extensible in the last release):

NOTE: Use the "e:" or "c:" filter strings in the object designer to see only e:xtended or c:ustomized elements, as shown in the screenshot.

Now we need to create a new class (a completely new class, no extensions here as it's a new module, not an extension of an existing one) with the following code:

/*
Detailed description of how to setup references for number sequences can
be found in method loadModule() on the superclass: NumberSeqApplicationModule.
*/
class NumberSeqModuleMSVRM extends NumberSeqApplicationModule
{
    protected void loadModule()
    {
        NumberSeqDatatype datatype = NumberSeqDatatype::construct();

        /* Insurance Provider Id */
        datatype.parmDatatypeId(extendedTypeNum(MSVRMInsuranceProviderId));
        datatype.parmReferenceHelp(literalStr('Provider ID'));
        datatype.parmWizardIsContinuous(false);
        datatype.parmWizardIsManual(NoYes::No);
        datatype.parmWizardIsChangeDownAllowed(NoYes::No);
        datatype.parmWizardIsChangeUpAllowed(NoYes::No);
        datatype.parmSortField(1);
        
        datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);
        this.create(datatype);
    }

    public NumberSeqModule numberSeqModule()
    {
        return NumberSeqModule::MSVRM;
    }

    [SubscribesTo(classstr(NumberSeqGlobal),delegatestr(NumberSeqGlobal, buildModulesMapDelegate))]
    static void buildModulesMapSubsciber(Map numberSeqModuleNamesMap)
    {
        NumberSeqGlobal::addModuleToMap(classnum(NumberSeqModuleMSVRM), numberSeqModuleNamesMap);
    }
}

A subscription to the buildModuleMapDelegate delegate allows us to add our new module to the standard number sequences Map without any over-layering. A lot of new delegates have been added to the standard objects lately to allow more points where a subscription can be done to change the behavior of standard code. Look for them as this is the way developers tell others where to put their modifications.

 

If you're an ISV yourself, add delegates to your own code to allow others to extend your modules without over-layering. This will make life easier for every parties, as upgrading the ISV code will be possible without any code conflict.

 

Hopefully some of the demonstrated techniques will be useful for those embracing the Extensions mindset. Unfortunately, there are still some scenarios that cannot be completely covered with extensions and over-layering is still needed. Event handlers and delegates are a good resource to minimize this over-layering impact, but we will talk about those in a different blog post.

If you feel that you have a business scenario that is impossible to achieve by using only extensions, please log an extension request explaining your case.

 

/* DISCLAIMER:
Microsoft provides programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability or fitness for a particular purpose.

This post assumes that you are familiar with the programming language that is being demonstrated and the tools that are used to create and debug procedures. */
 
NOTE: Updated 11/08/2017: Fixed second code example.

Skip to main content