AX 7. Link class instances to a form.

In one of my previous blogs we discussed how to override form control methods or form data source field methods without overlaying using registerOverrideMethod(). But what if we need to link class instance to a form? We have two options: use form extension classes with [ExtensionOf] attribute or register class instance on the form using formRun.registerExtensionInstance() method. Main difference between them is that with extension classes you add new methods or state extending form functionality and using registerExtensionInstance() we link class instance with new logic to a from, so we can have one class that could handle multiple forms. First approach is described in this blog post, so today we will focus on the second one.

Under the hood FromRun class has map variable to store registered instances:

Map formExtensionInstance = new Map(Types::string, Types::Class);

And 3 methods to work with it:

/// <summary>
/// Registers an extension instance.
/// </summary>
/// <param name="_extensionKey">The key for the extension.</param>
/// <param name="_extensionInstance">The instance for the extension.</param>
public void registerExtensionInstance(str _extensionKey, Object _extensionInstance)
    formExtensionInstance.insert(_extensionKey, _extensionInstance);
/// <summary>
/// Gets a registered extension instance.
/// </summary>
/// <param name="_extensionKey">The key for the extension.</param>
/// <returns>The instance for the extension if one is found.</returns>
public Object getExtensionInstance(str _extensionKey)
    return formExtensionInstance.lookup(_extensionKey);
/// <summary>
/// Gets a Boolean value indicating if an extension with the specified key is registered.
/// </summary>
/// <param name="_extensionKey">The key for the extension.</param>
/// <returns>true if an exteinsion with the specified key is registered; otherwise; false.</returns>
public boolean hasExtensionInstance(str _extensionKey)
    return formExtensionInstance.exists(_extensionKey);

There is a common way in standard code how to build extension classes:

  1. Create new class and add variable to store FormRun object, flag that indicates if instance is initialized and other required variables. Let’s add FormStringControl for example.
public class MySampleExtension
    FormRun element;
    boolean initialized;

    FormStringControl myFormStringControl;
  1. Create new method to initialize FormRun and register current instance on the form.
public void new(FormRun _formRunInstance)
    initialized = false;
    element     = _formRunInstance;

    // register this extension with the main form
    element.registerExtensionInstance(classStr(MySampleExtension), this);
  1. Create init() method to initialize all required variables and register override if needed.
private void init()
    if (initialized)

    initialized = true;

    myFormStringControl =, MyFormStringControl));

    //register overrides here if needed
  1. Subscribe to form pre init() event to register extension instance.
[FormEventHandler(formStr(MySampleForm), FormEventType::Initializing)]
public static void MySampleForm_OnInitializing(xFormRun _sender, FormEventArgs _e)
    MySampleExtension extensionInstance = new MySampleExtension(sender);
  1. Subscribe to form post init() event to initialize extension instance.
[FormEventHandler(formStr(MySampleForm), FormEventType::Initialized)]
    // Initialize the instance of this form extension handler now that the controls exist
    FormRun mySampleForm = _sender as FormRun;
    MySampleExtension extensionInstance  = mySampleForm.getExtensionInstance(classStr(MySampleExtension));

  1. Use extension class
[FormControlEventHandler(formControlStr(MySampleForm, MySampleButton), FormControlEventType::Clicked)]
public static void mySampleButton_OnClicked(FormControl _sender, FormControlEventArgs _e)
    if (_sender && _sender.formRun())
        FormRun formRun = _sender.formRun();

        MySampleExtension extensionInstance = formRun.getExtensionInstance(classStr(MySampleExtension));


public void mySampleButtonClicked()

That’s pretty much all of it.

AX 7. Extending forms using Form Parts.

Update 3 brought significant improvement to a form extension experience – ability to add form parts. Why it’s important? Previously we were able to add new data sources, new controls or modify some properties of existing controls. However, we did not have option to override form data source methods, we were able to use events only and it is not enough in some cases. Classic example is adding new InventDim data source on a form, where you need to comment out super() of data source write() method.

Now you can create new form, where you have full control over all the code and use it as a form part on a form extension.

Let’s look how it’s done in a standard application using CaseDetail.Extension form extension as an example.


This extension has a couple of new form parts. Each form part is a simple form:


As you can see, it has one data source and a group with several fields. Here it is used to show fields in a details format, however, you can use grid and action panes if you want. A good example is Addresses and Contact information tabs on customer form that are done with form parts as well.

The most interesting part is how to link new form part with form extension. First, you need to add new “From part” control to a form extension design and specify display menu item name of a form part. After that you will see new “Links” node.  It supports four different types of links:

Field relation link Link the form data source with the form part data source by the field.
Field value link Link the form data source with the form part data source by specifying fixed value for the form part data source field.
Table relation link Link the form data source with the form part data source using relation on the form data source table to the form part data source table.
Target table relation link Link the form data source with the form part data source using relation on the form part data source table to the form data source table.

Choose link type that suits, set properties and you are good to go!

AX 7. URLUtility extension to create deep links.

In AX 7 we can use URL to open any form and it’s quite handy. Out of the box there is a feature called “Get link” that helps us to generate and share links. We can find it under Options -> Share -> Get Link.


However, it has a limitation – you cannot generate link to open a specific record (so called “deep links”).

We can generate deep links from code by using URLGenerator class (there is a wiki article with explanations ), but it requires quite a bit of code to write. So I did small extension to URLUtility class to simplify this process.

It adds two new methods: generateRecordUrlFromDataSource and generateRecordUrl.

  • generateRecordUrlFromDataSource accepts form data source and generates a link to the record based on current record primary index fields.
  • generateRecordUrl accepts all parameters that are required to open a form, like menu item name and type, form data source name and map with key field’s IDs and values.
using Microsoft.Dynamics.AX.Framework.Utilities;
using Microsoft.Dynamics.@Client.ServerForm.Contexts;

/// <summary>
/// The class <c>URLUtility_Extension</c> contains extension methods for the <c>URLUtility</c> class.
/// </summary>
public static class URLUtility_Extension
    public static str generateRecordUrl(str _menuItemName, MenuItemType _menuItemType, DataSourceName _dataSourceName, Map _indexFieldValuesMap, DataAreaId _dataAreaId = curExt())
        System.Uri host                     = SessionContext::Get_Current().Get_RequestUrl();
        UrlHelper.UrlGenerator generator    = new UrlHelper.UrlGenerator();
        generator.MenuItemName              = _menuItemName;
        generator.MenuItemType              = _menuItemType;
        generator.HostUrl                   = host.GetLeftPart(System.UriPartial::Path);
        generator.Company                   = _dataAreaId;
        generator.EncryptRequestQuery       = true;

        if (_dataSourceName && _indexFieldValuesMap)
            MapEnumerator mapEnumerator = _indexFieldValuesMap.getEnumerator();

            var requestQueryParameterCollection = generator.RequestQueryParameterCollection;

            while (mapEnumerator.moveNext())
                requestQueryParameterCollection.UpdateOrAddEntry(_dataSourceName, mapEnumerator.currentKey(), mapEnumerator.currentValue());

        return generator.GenerateFullUrl().AbsoluteUri;

    public static str generateRecordUrlFromDataSource(FormDataSource _formDataSource)
        FormRun         formRun         = _formDataSource.formRun();
        str             menuItemName    = formRun.args().menuItemName();
        MenuItemType    menuItemType    = formRun.args().menuItemType();
        DataSourceName  dataSourceName  =;

        TableId   tableId   = _formDataSource.table();
        DictTable dictTable = new DictTable(tableId);
        DictIndex dictIndex = new DictIndex(tableId, dictTable.primaryIndex());

        int     fieldCount          = dictIndex.numberOfFields();
        Map     indexFieldValuesMap = new Map(Types::String, Types::String);
        Common  record              = _formDataSource.cursor();
        FieldId primaryKeyFieldId;

        for (int fieldIndex = 1; fieldIndex <= fieldCount; fieldIndex++)
            primaryKeyFieldId = dictIndex.field(fieldIndex);

            indexFieldValuesMap.insert(fieldId2Name(tableId, primaryKeyFieldId), any2Str(record.(primaryKeyFieldId)));

        return URLUtility::generateRecordUrl(menuItemName, menuItemType, dataSourceName, indexFieldValuesMap, record.DataAreaId);

To test this class, you can use simple job:

class RunnableClass1
    public static void main(Args _args)
        Map indexFieldValuesMap = new Map(Types::String, Types::String);
        indexFieldValuesMap.insert(fieldStr(VendTable, AccountNum), '1002');

        Box::info(URLUtility::generateRecordUrl(menuItemDisplayStr(VendTable), MenuItemType::Display, identifierStr(VendTable), indexFieldValuesMap, 'usmf'));

It’s an initial version, so feel free to post comments in case of any bugs!

AX 7. Trick to override method in derived class without overlaying.


The VendDocumentLineType class has several subclasses that handle specific types of vendor document lines. We want to change a behaviour for invoice lines and implement new logic for Purchase Unit field defaulting.

Solution using overlaying:

Overlay VendDocumentLineType_Invoice class and override determineDefaultPurchUnit method.

Solution without overlaying:


1) Create class derived from VendDocumentLineType_Invoice:

/// <summary>
/// The <c>VendDocumentLineType_Invoice_Sample</c> class is used for validation and applying default values to invoice lines.
/// </summary>
class VendDocumentLineType_Sample extends VendDocumentLineType_Invoice

2) Override determineDefaultPurchUnit method:

protected PurchUnit determineDefaultPurchUnit()
    // implement new behaviour
    return VendParameters::find().DefaultPurchUnit_Sample;

3) Create post event handler for VendDocumentLineType constructor to replace VendDocumentLineType_Invoice with new implementation:

/// <summary>
/// Handles events raised by <c>VendDocumentLineType</c> class.
/// </summary>

public static class VendDocumentLineTypeEventHandler_Sample
    /// <summary>
    /// Post event handler for VendDocumentLineType.createFromTable(...)
    /// </summary>
    /// <param name="_args"></param>
    [PostHandlerFor(classStr(VendDocumentLineType), staticMethodStr(VendDocumentLineType, createFromTable))]
    public static void VendDocumentLineType_Post_createFromTable(XppPrePostArgs _args)
        // get params from args
        VendDocumentLineMap vendDocumentLineMap = _args.getArg(identifierStr(_vendDocumentLineMap));
        PurchLine           purchLine           = _args.getArg(identifierStr(_purchLine));
        PurchParmUpdate     purchParmUpdate     = _args.getArg(identifierStr(_purchParmUpdate));

        // construct and init new object
        VendDocumentLineType strategy = VendDocumentLineType_Invoice_Sample::createFromTable(vendDocumentLineMap, purchLine, purchParmUpdate);

        // replace return value only if new strategy is created; otherwise use standard
        if (strategy)
           // replace return value with new implementation


4) Implement methods that will instantiate and initialize VendDocumentLineType_Invoice_Sample class:

/// <summary>
/// Constructs a new instance of a <c>VendDocumentLineType_Invoice_Sample</c> class derivative.
/// </summary>
/// <param name="_vendDocumentLineMap">
/// A <c>VendDocumentLineMap</c> record.
/// </param>
/// <param name="_purchLine">
/// A <c>PurchLine</c> table record that is used when you apply the default values; optional.
/// </param>
/// <param name="_purchParmUpdate">
/// A <c>PurchParmUpdate</c> table record that is used when you apply the default values; optional.
/// </param>
/// <returns>
/// A <c>VendDocumentLineType_Invoice_Sample</c> class.
/// </returns>
public static VendDocumentLineType_Invoice_Sample createFromTable(VendDocumentLineMap _vendDocumentLineMap, PurchLine _purchLine = null, PurchParmUpdate _purchParmUpdate = null)
    VendDocumentLineType_Invoice_Sample strategy;

    // VendDocumentLineType_Invoice is created only for this 2 types
    if (_vendDocumentLineMap.Ordering == DocumentStatus::Invoice ||
        _vendDocumentLineMap.Ordering == DocumentStatus::ApproveJournal)
        strategy = new VendDocumentLineType_Invoice_Sample();
        strategy.init(_vendDocumentLineMap, _purchLine, _purchParmUpdate);

    return strategy;

public void init(VendDocumentLineMap _vendDocumentLineMap, PurchLine _purchLine, PurchParmUpdate _purchParmUpdate)
    // code copied from VendDocumentLineType::createFromTable to init new instance
    this.physicalStrategy(VendDocumentLineTypePhysical::createFromTable(this, _vendDocumentLineMap));

You can apply this approach to any class hierarchy in AX that has construct method (like SalesLineType, PurchLineType, etc.) when you need to override methods in derived class to implement new behaviour.

AX 7. Get information about table extension using Microsoft.Dynamics.Ax.Xpp.MetadataSupport

In AX 2012 we used DictTable class to get information about tables, table fields, indexes and field groups. However we cannot retrieve any information about table extensions using this class, so Microsoft.Dynamics.Ax.Xpp.MetadataSupport was introduced in AX 7.

Let’s look at simple job that will return all table extensions and all fields from table extensions for CompanyInfo table.

public static void main(Args _args)
    var axTableExtensions = Microsoft.Dynamics.Ax.Xpp.MetadataSupport::GetTableExtensionsForTable(tableStr(CompanyInfo));
    System.Collections.IEnumerator axTableExtensionsEnumerator = axTableExtensions.GetEnumerator();

    while (axTableExtensionsEnumerator.moveNext())
        Microsoft.Dynamics.AX.Metadata.MetaModel.AxTableExtension axTableExtension = axTableExtensionsEnumerator.get_Current();

        info(strFmt("Extension name %1", axTableExtension.Name));

        var axTableExtensionFields = axTableExtension.Fields;

        System.Collections.IEnumerator axTableExtensionFieldsEnumerator = axTableExtensionFields.GetEnumerator();

        while (axTableExtensionFieldsEnumerator.moveNext())
            Microsoft.Dynamics.AX.Metadata.MetaModel.AxTableField axTableExtensionField = axTableExtensionFieldsEnumerator.get_Current();

            info(strFmt("Field name %1", axTableExtensionField.Name));
            info(strFmt("Mandatory %1", axTableExtensionField.Mandatory));
            info(strFmt("Allow edit %1", axTableExtensionField.AllowEdit));

AX 7. Accessing private\protected class methods and members from extension code.

All class member variables are protected by default in AX 7, so it is impossible to access them from extensions. It becomes a real problem when you try to extend classes like SalesLineType.

For example, we want to add custom logic on sales line insert event. Call to super() is commented out so we cannot create pre or post event handlers. We may try to create event handlers for SalesLineType.insert() mehtod, but we won’t get access to salesLine buffer because this variable is protected.

There are two options: use overlaying or use reflection.

Overlaying is preferable approach, but today we will talk about reflection to explain this option.

Reflection is usually used for unit testing in case you need to cover protected or private code and it is hard to call this code using public API.

It has bunch of disadvantages:

  • It breaches entire basis of OO programming.
  • Slow performance.
  • Possible issues with future updates. Private methods could be changed at any time.

However, once you may get into situation where it could be the only option so it’s better to know about this possibility.

Let’s try to change some fields on sales line insert using reflection.

Create new event handler class for SalesLineType and subscribe to post insert:

using System.Reflection;

/// Handles events raised by <c>SalesLineTypeEventHandler</c> class.
/// </summary>

public class SalesLineTypeEventHandler
    [PostHandlerFor(classStr(SalesLineType), methodStr(SalesLineType, insert))]
    public static void SalesLineType_Post_insert(XppPrePostArgs _args)
        SalesLineType salesLineType = _args.getThis();

        var bindFlags = BindingFlags::Instance | BindingFlags::NonPublic;

        var field = salesLineType.GetType().GetField("salesLine", bindFlags);

        SalesLine salesLine = field.GetValue(salesLineType);

        if (salesLine)
            salesLine.MyNewField = 42;

Also we can call private or protected method:

var bindFlags = BindingFlags::Instance | BindingFlags::NonPublic;

var methodInfo = salesLineType.GetType().GetMethod(methodStr(SalesLineType, checkQuantityUpdate), bindFlags);

if (methodInfo)
    methodInfo.Invoke(salesLineType,  new System.Object[0]());

You can read more about reflection on msdn

Thanks to Simon Buxton for rising this on yammer.

AX 7. How to add financial dimensions as separate columns to Data Entity.

Everyone who is working with financial dimensions in AX would like to see them in separate columns in Excel and today we will look how to achieve this for all Data Entities.


It’s also awesome because we have wizard that will do all the job for us!

In the Visual Studio under Dynamics AX > Addins select “Add financial dimensions for OData…”


Now we need to enter dimensions we want to expose. As you can see from a help text this dimensions should be setup in AX on Data Entities tab under General ledger > Chart of accounts > Dimensions > Financial dimension configuration for integrating applications.


Also we need to specify a model for new objects. Please note that the model should have reference to the Dimensions model, however you can add reference afterwards.

After clicking “Apply”, VS will show new project creation dialog. New project will be created. It will contain two Data Entity Extensions:


Please note that you cannot rename them because internal code works only with entity extensions that have name = Data Entity Name + “DimensionIntegration”.

Both entities has identical changes, the only difference is that one show default dimensions and another one ledger dimensions.

Let’s looks at DimensionSetEntity extension:


It has 3 new fields, one for each financial dimensions we specified in the wizard.

This fields are computed columns, each of them use same data method “getValueColumnForDimensionAttributeSql”.


It is quite interesting and has some smarties inside but hardcoded values as well.

That all we need to do. After compilation and synchronization we can open Data Entity in Excel and will see new fields in data connector designer.


How does it work? That the most exciting part. All the magic is inside DimensionAttributeValueSet table. If we look at it in AOT – nothing is really changed since AX 2012, but if we go to SQL management studio we will see bunch of new columns:


For each financial dimensions we have 2 columns: one contains value and another RecId.

Each computed column in Dimensions entity select value from this table.

So, if you are doing BI with 3rd party tools, now you can use  DimensionAttributeValueSet and DimensionAttributeValueCombination tables to query all financial dimensions directly.