Creating a class, the best practice way

May 18, 2010 at 13:40
filed under Dynamics AX
Tagged , , ,

Hi all!

Some time ago, I talked about the ‘construct’ pattern that is use a lot in AX. Although I use it often, it’s not perfect because it violates some best practices. (Also, you could end up with a large class hierarchy of classes that extend each other, but I’ll discuss that in an other post.)

First off, know that you can change the compiler level and the best practices the compiler checks.
To do this, compile something so the compiler output window pops up. Then click the setup button, and adjust the settings.
I used compiler level 4 and enabled all BP checks for this example.

So, let create a simple class. This class will simply print information of an InventTable record to the screen.

1. ClassDeclaration
When we think about this class a bit, we immediately realize that we need to declare an InventTable record as a member of out class:

/// <summary>
/// Class written to test construct and new... patterns
/// </summary>
/// <remarks>
/// Delete this class when proven a point :)
/// </remarks>
class KLFORTestBP
{
    InventTable inventTable;
}

Also note that I added documentation to this method. I will do so for other methods too. When you don’t, you’ll get a BP deviation warning.

2. Parm method
One of the things that are important (or at least I find important), is using parm methods for your class variables. So, let’s create one:

// use parm methods to access class variables
public InventTable parmInventTable(InventTable _inventTable = inventTable)
{
    ;
    inventTable = _inventTable;

    return inventTable;
}

3. New method
You should never write code in the new method, or add parameters to it. On the contrary, you should always set this method to protected, and use a static construct() or a static new…() method instead.

// a new method should be protected
// use static new... or construct instead
protected void new()
{
}

4. Run method
The run method will be our method that contains our logic. Here, we print info to the screen

/// <summary>
/// Executes logic on the parameters of this class
/// </summary>
/// <remarks>
/// Just for demo purposes
/// </remarks>
public void run()
{
    ;
    info(this.parmInventTable().NameAlias);
}

Note that we are using the parm method instead of directly accessing the variable.

5. Construct method
Because the new method is now protected, we will need a contruct method to be able to create an instance of this class.

private static KLFORTestBP construct()
{
    ;
    return new KLFORTestBP();
}

When a class doesn’t need parameters, you can leave out the ‘private’, but now, it is private because we plan on creating a new…() method that contains parameters. A construct method shouldn’t contain parameters, and should only be used to create a new instance of your class. It is also the only place in your application where you can call the new() method of your class.

6. New…() method
As said earlier, because we need a parameter (an InventTable record), we use a new…() instead of the construct method to create an instance of our class. I will name it newFromInventTable() based on the parameter it has.
In this method, we call the contruct() method to create an instance (instead of new()), set the parm method from the parameter and return the object.

/// <summary>
/// Creates a new KLFORTestBP object from an InventTable record
/// </summary>
/// <param name="_inventTable">
/// The InventTable used to create the object
/// </param>
/// <returns>
/// New instance of KLFORTestBP with the InventTable parameter set
/// </returns>
/// <remarks>
/// Use a new... method instead of the constuct method because it takes parameters/// A
/// </remarks>
public static KLFORTestBP newFromInventTable(InventTable _inventTable)
{
    KLFORTestBP kLFORTestBP;
    ;

    kLFORTestBP = KLFORTestBP::construct();
    kLFORTestBP.parmInventTable(_inventTable);

    return kLFORTestBP;
}

You could also create an init() method that initializes you class (we don’t need initialization now). The init() method should return a boolean if initialization can go wrong, so you can throw an error.
It would look like this:

public static KLFORTestBP newFromInventTable(InventTable _inventTable)
{
    KLFORTestBP kLFORTestBP;
    ;

    kLFORTestBP = KLFORTestBP::construct();
    kLFORTestBP.parmInventTable(_inventTable);

    if(!kLFORTestBP.init())
    {
        throw error("Item record has no data"); // todo: create label
    }

    return kLFORTestBP;
}

Remember to create a label for the text string, or you will get a best practice error.

7. Main method
Now, all we need more is a main method, so this class is runable (by pressing F5 or as a menu item).

server public static void main(Args _args)
{
    KLFORTestBP kLFORTestBP;
    InventTable inventTable;
    ;

    if(_args && _args.dataset() == tablenum(InventTable))
    {
        // a button was pressed on a InventTable datasource
        inventTable = _args.record();
    }
    else
    {
        // for demo, simple select the first one
        select firstonly inventTable;
    }

    // display item information
    kLFORTestBP = KLFORTestBP::newFromInventTable(inventTable);
    kLFORTestBP.run();
}

Normally, you will get the record from the args object, but for demo purposes, I added the ‘select firstonly’ so you can run the class by pressing F5. As you can see, the new…() method is used to create an instance of the class, and then the run method is called.

There you go, you now have a best practice deviation free class!

8. Using the class
Using the class as a menu item or in code couldn’t be easier.

You can drag/drop the class to the action menu item node in the AOT to create a menu item, and you will be able to use it on any form with the inventTable datasource.

From code, you can simple write this line (assuming there is a variable called inventTable):

KLFORTestBP::newFromInventTable(inventTable).run();

Conclusion:
When writing code that doesn’t contain any best practice deviations (especially when checking them all on compiler level 4), you quickly end up with a lot of methods in your class and wondering if the effort is worth it. It can be a bit painful to get your code best practice deviation free.

However, you’ll also realise that following the best practices has its advantages. Your code will be cleaner, easier to understand, easier to debug because of the parm, constuct and run methods, less subject to change, contain less errors and your class will be used in the way you intended when you wrote it.

More info on MSDN:
Best practices for constructors

11 comments

RSS / trackback

  1. Deepak Kalra

    I need to access InventTable Instance of main Method in the dialog method.Please suggest how could I?

  2. Klaas Deforche

    Hi,

    You can do this:

    if(kLFORTestBP.prompt())
    {
        kLFORTestBP.run();
    }

    Then in your dialog, just use the parm method (or use the inventTable variable for the class declaration directly.

    Does that answer your question?

  3. lekhanh

    Hi Klass,
    I have a question about Classes object in MS AX. When I creat new class in AX, how can I use that class in Visual Studio?
    Thank so much.

  4. Klaas Deforche

    Hi lekhanh,

    You can use the .NET business connector in VS.NET to call logic in AX:
    http://msdn.microsoft.com/en-us/library/aa851743.aspx#Y453

    You could also create a service in AX, and expose it on a seb server (IIS), then consume the web service. After a quick search on google I found this:
    http://blogs.msdn.com/b/aif/archive/2008/12/16/creating-custom-dynamics-ax-services.aspx
    It’s a bit a a pain to create a service like this though, but this will be improved in AX 2012.

    For now, I would stick to the .NET business connector.

  5. XppDotNet

    Hi Lekhanh – XppDotNet is a free tool that helps expose Ax business logic for .NET applications and services. Download at http://www.xppdotnet.com.

  6. Carlo Heuberge

    Why do we need a private construct() method?
    It should be possible to call the protected new() from anywhere inside the class, that is, the same places as it is possible to call the private construct() method!

  7. Klaas Deforche

    Hi Carlo,

    On MSDN it says:
    In most cases the construct method should be public. If the class is instantiated using a newParameter method, then declare the construct method as private. (http://msdn.microsoft.com/en-us/library/aa637432.aspx).

    I think the resoning behind this is that a static new should always return a valid object when it is called from an other class than where it resides. When you have a new…() method, you should hide the construct in all cases because it will never return a valid object.
    When you adhere to the best practice of new using the new() method to instantiate object, then you will never see a constructor pop up in intellisense that you shouldn’t be using.
    A nice side effect of this is that if you dupplicate a class (lazy developers :), the new…() method that was copied as well will not compile because the construct you use there is private. This will remind you to change the constructor.

    You actually can call both protected and private methods on the class that resides. The difference is when you want to construct the class from a class that extends it, then only the protected methods can be called.
    When the new…() method doesn’t fit your needs on ‘child’ classes, then you’ll have to use the protected new method of the parent. But in that case, you know what you are doing scince you are in your own class hierarchy. To be honest, I don’t think I’ve ever called the new() method of a parent class in a child class.

    But you know, it is also a matter of taste. I always set the new method protected and in case I need parameters to construct a class, it always set the construct to private. In AX, most of the classes don’t even have the new set to protected so even if you do only that, you’re still doing better than most :).

    Thanks for responding, I will think about this some more…

  8. prorook

    FYI…if you are extending RunBase and using a prompt, you need to be cautious of setting parameters(class variables) BEFORE you run runbase.prompt().
    The prompt method will reset all of the packed variables. It just grabs the values from your usage data.

  9. Trackbacks

respond