Previously, we used to define class constructors like below if we want to switch correct child class via a parameter :


public static AtTest constructFromCommon(Common _common)
 {
 AtTest ret;

switch(_common.Tableid)
 {
 case tableNum(CustTable) :
 ret = new AtTest_Customer(_common);
 break;
 case tableNum(VendTable) :
 ret = new AtTest_Vendor(_common);
 break;
 }

return ret;

}

Since AX 2012, we have the ability to create and use .NET attributes and in D365 FO they are widely used in the extension framework.

The constructor template given above is declared in the base class and child classes does not have any information on them about which table they are constructed from, which makes it not very readable. Moreover, to be able to remove a class from here or add another child class, we needed to modify the ‘switch’ clause via overlayering (in older AX versions), which is not very practical and may cause problems on code upgrades. In D365 FO, we have another concern of being ‘extendable’. It is possible to extended those old style constructs using method posthandlers but if a couple of different extensions extend the same method, things get a little bit messy.

For all that reasons, class attributes are the new way to define child class constructors and widely being used in newer D365 FO code. For an example, see the class “InventTransferPrintCommand” and its child classes.

Let create an example to understand it further. To be able to use attributes in class constructors, you need to define your attribute class first. It is done by extending the SysAttribute class and SysExtensionIAttribute interface, then declaring your attribute parameters as properties as shown in the template belown :

///
<summary>
/// The <c>AtTableNameAttribute</c> is the attribute for <c>AtTest</c> classes.
/// </summary>

class AtTableNameAttribute extends SysAttribute implements SysExtensionIAttribute
{
    TableName tableName;

    void new(TableName _tableName)
    {
        super();

        tableName = _tableName;
    }

    ///
<summary>
    /// Returns the key used for storing cached data for this attribute.
    /// </summary>

    /// <returns>
    /// A string representing the cache key.
    /// </returns>
    public str parmCacheKey()
    {
        return classStr(AtTableNameAttribute)+';'+tableName;
    }

    public TableName parmTableName(TableName _tableName = tableName)
    {
        tableName = _tableName;

        return tableName;
    }

    ///
<summary>
    /// Determines if the same instance should be returned by the extension framework.
    /// </summary>

    /// <returns>
    /// true if the same instance should be returned; otherwise false.
    /// </returns>
    public boolean useSingleton()
    {
        return false;
    }

}

The parmCacheKey() method should return a unique key per class + parameter combination and used for caching the attribute class instances per parameter. Set singleton false if you do want to use an instance per parameter (in our case yes).

Another thing you can spot here is the usage of TableName instead of tableId directly. At the time of writing, in the attribute declaration, you can only use metadata assertion functions like tableStr(), classStr() and you cannot use the compile-time tableNum() function.

I have created following classes to test an attribute based child class construction. It simply constructs a child class per table Customer or Vendor and returns their name from the parmName method to test. The constructor of the base class was the code shown in the beginning, using a “switch” to construct the correct child class. After we implement attributes on our child classes, our construct is now changed to benefit from the SysExtensionAppClassFactory::getClassFromSysAttribute() method instead of using a switch statement:

public class AtTest
{
    protected Name	name;
    protected Common	common;

    public void initFromCommon(Common	_common)
    {
        common = _common;
    }

	public Name parmName(Name _name = name)
    {
        name = _name;

        return name;
    }

	public static AtTest constructFromCommon(Common _common)
    {
        AtTest ret;
        AtTableNameAttribute	tableNameAttribute = new AtTableNameAttribute(tableId2Name(_common.TableId));

        ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(AtTest), tableNameAttribute);
        ret.initFromCommon(_common);

        return ret;
    }

}

[AtTableNameAttribute(tableStr(CustTable))]
class AtTest_Customer extends AtTest
{
    public void initFromCommon(Common	_common)
    {
        CustTable custTable = _common;

        super(_common);

        name = custTable.name();
    }
}

[AtTableNameAttribute(tableStr(VendTable))]
class AtTest_Vendor extends AtTest
{
    public void initFromCommon(Common	_common)
    {
        VendTable vendTable = _common;

        super(_common);

        name = vendTable.name();
    }
}

Realize that we no longer have to modify the construct method of the base class. If we want to add another child class to AtTest class, we just create another child class and set the correct attribute on top of its declaration. One drawback is, at the time of writing, you cannot pass parameters to the new() method of the class if you construct it from the getClassFromSysAttribute() method. You have to construct the class itself without parameters and then pass parameters using initFrom__ or parm__ methods. So your older classes may need some re-construction as we did in our example here.

You can download the example code from the link below :

https://github.com/sertanyaman/SertanDevAXExamples/blob/master/AttributeTest.zip

 

Leave a Reply