Miklix

Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012

Published: October 7, 2020 at 5:29:45 PM UTC

This article describes how to use the little-known SysExtension framework in Dynamics AX 2012 and Dynamics 365 for Operations to instantiate sub classes based on attribute decorations, allowing for an easily extensible design of a processing class hierarchy.


The information in this post is based on Dynamics AX 2012 R3. It may or may not be valid for other versions. (Update: I can confirm that the information in this article is also valid for Dynamics 365 for Operations)

When implementing processing classes in Dynamics AX, you're often faced with creating a class hierarchy in which each subclass corresponds to an enum value or has some other data coupling. A classic design is to then have a construct method in the super class, which has a switch that determines which class to instantiate based on the input.

This works well in principle, but if you have many different possible inputs (many elements in an enum or perhaps the input is a combination of several different values), it can become tedious and error-prone to maintain and the design always has the disadvantage that you will need to modify said construct method if you ever add a new subclass or make changes to which subclass should be used based on which input.

Fortunately, there is a much more elegant, but unfortunately also much less known, way of doing this, namely by use of the SysExtension framework.

This framework takes advantage of attributes that you can use to decorate your sub classes to make the system able to figure out which sub class should be used for handling what. You will still need a construct method, but if done correctly, you will never have to modify it when adding new sub classes.

Let's look at an imaginary example and say that you're going to implement a hierarchy that does some sort of processing based on the InventTrans table. Which processing to do depends on the StatusReceipt and StatusIssue of the records, as well on whether the records are related to SalesLine, PurchLine or neither. Already now, you're looking at a lot of different combinations.

Let's then say that you know that for now you only need to handle a handful of the combinations, but you also know that you will be asked to be able to handle more and more combinations over time.

Let's keep it relatively simple and say that for now you only need to handle records related to SalesLine with a StatusIssue of ReservPhysical or ReservOrdered, all other combinations can be ignored for now, but since you know you will have to handle them later, you'll want to design your code in a way that makes it easily extensible.

Your hierarchy may look something like this for now:

  • MyProcessor
    • MyProcessor_Sales
      • MyProcessor_Sales_ReservOrdered
      • MyProcessor_Sales_ReservPhysical

Now, you could easily implement a method in the super class that instantiates a subclass based on a ModuleInventPurchSales and a StatusIssue enum. But you will then need to modify the super class every time you add a sub class, and that is not really the idea of inheritance in object-oriented programming. Afterall, you don't need to modify RunBaseBatch or SysOperationServiceBase every time you add a new batch job.

Instead, you can make use of the SysExtension framework. That will require you to add another class, which needs to extend SysAttribute. This class will be used as the attribute you can decorate your processing classes with.

This class is very similar to how you would make a data contract class for a SysOperation implementation, in that it will have some data members and parm methods for getting and setting those values.

In our case, the ClassDeclaration may look something like this:

class MyProcessorSystemAttribute extends SysAttribute
{
    ModuleInventPurchSales  module;
    StatusIssue             statusIssue;
    StatusReceipt           statusReceipt
}

You need to make a new() method for instantiating all data members. If you wish you can give some or all of them default values, but I haven't done that.

public void new(ModuleInventPurchSales  _module,
                StatusIssue             _statusIssue,
                StatusReceipt           _statusReceipt)
{
    ;

    super();

    module          = _module;
    statusIssue     = _statusIssue;
    statusReceipt   = _statusReceipt;
}

And you should also implement a parm method for each data member, but I've omitted those here as I'm sure you know how to do that - otherwise, let's consider it an exercise ;-)

Now you can use your attribute class to decorate each of your processing classes. For example, the class declarations could look like this:

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::None,
                            StatusReceipt::None)]
class MyProcessor_Sales extends MyProcessor
{
}

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::ReservOrdered,
                            StatusReceipt::None)]
class MyProcessor_Sales_ReservOrdered extends MyProcessor_Sales
{
}

[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
                            StatusIssue::ReservPhysical,
                            StatusReceipt::None)]
class MyProcessor_Sales_ReservPhysical extends MyProcessor_Sales
{
}

You can of course name your classes any way you want, the important part here is that you decorate your classes with attributes that correspond to what kind of processing they do. (But bear in mind that there are naming conventions for class hierarchies in Dynamics AX and it's always a good idea to follow those, if possible).

Now that you have decorated your classes to identify what kind of processing each of them does, you can take advantage of the SysExtension framework to instantiate objects of the sub classes as needed.

In your super class (MyProcessor), you could add a construct method like this:

public static MyProcessor construct(ModuleInventPurchSales _module,
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
    MyProcessor                 ret;
    MyProcessorSystemAttribute  attribute;
    ;

    attribute = new MyProcessorSystemAttribute( _module,
                                                _statusIssue,
                                                _statusReceipt);

    ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MyProcessor), attribute);

    if (!ret)
    {
        //  no class found
        //  here you could throw an error, instantiate a default
        //  processor instead, or just do nothing, up to you
    }

    return ret;
}

The really interesting part - and really the object (pardon the pun) of this entire post - is the getClassFromSysAttribute() method in the SysExtensionAppClassFactory class. What this method does is that it accepts the name of the super class of a hierarchy (and this super class does not need to be at the top of the hierarchy; it simply means that only classes extending this class will be eligible) and an attribute object.

It then returns an object of a class that extends the specified super class and is decorated with a corresponding attribute.

You can obviously add as much further validation or logic to the construct method as you wish to, but the important takeaway here is that once implemented, you should never have to modify this method again. You can add sub classes to the hierarchy and as long as you make sure to decorate them appropriately, the construct method will find them even though they didn't exist when it was written.

What about performance? I honestly haven't attempted to benchmark it, but my gut feeling is that this probably performs worse than the classic switch statement design. However, considering that by far the most performance issues in Dynamics AX are caused by database access, I wouldn't worry too much about it.

Of course, if you're implementing something that will require thousands of objects to be created quickly, you may want to investigate further, but in the classic cases where you just instantiate a single object to do some lengthy processing, I doubt it'll matter. Also, considering my troubleshooting tip (next paragraph), it appears that the SysExtension framework relies on caching, so in a running system I doubt it has a significant performance hit.Troubleshooting: If the construct method doesn't find your sub classes even though you're certain they're decorated correctly, it may be a caching problem. Try clearing caches on both client and server. It shouldn't be necessary to actually restart the AOS, but it may be last resort.

Share on BlueskyShare on FacebookShare on LinkedInShare on TumblrShare on XShare on LinkedInPin on Pinterest

Mikkel Bang Christensen

About the Author

Mikkel Bang Christensen
Mikkel is the creator and owner of miklix.com. He has over 20 years experience as a professional computer programmer/software developer and is currently employed full-time for a large European IT corporation. When not blogging, he spends his spare time on a vast array of interests, hobbies, and activities, which may to some extent be reflected in the variety of topics covered on this website.