AX / D365FO – How to Dynamically Access Custom Attribute Names in Dynamics 365 FO Using X++

In Dynamics 365 Finance and Operations (D365FO), leveraging data contracts with attributes is crucial for defining parameters in the SysOperation framework. However, accessing these attributes dynamically, especially those decorated with custom names using [DataMemberAttribute], can be a challenge. In this article, we’ll explore how to retrieve attribute names dynamically in X++ by utilizing .NET reflection and CLR interop.

Why This Matters

When working with data contracts, it’s common to decorate properties with [DataMemberAttribute] to define custom names. For instance:

[DataContractAttribute]
public class MyContract
{
    [DataMemberAttribute('OPRNUM')]
    public str parmOperationNumber(str _operationNumber = operationNumber)
    {
        operationNumber = _operationNumber;
        return operationNumber;
    }
    private str operationNumber;

    [DataMemberAttribute('QTY')]
    public int parmQuantity(int _quantity = quantity)
    {
        quantity = _quantity;
        return quantity;
    }
    private int quantity;
}

In this example, we’ve defined custom attribute names, but accessing these names dynamically at runtime requires a different approach.

The Challenge

By default, X++ doesn’t provide a built-in way to dynamically iterate through and access custom attribute names. This can lead to tedious, hard-coded solutions that are prone to errors and maintenance issues.

The Solution: Using .NET Reflection with CLR Interop

The key to solving this challenge is to leverage the powerful .NET reflection capabilities available through CLR interop in X++. Here’s how you can achieve this:

Step-by-Step Solution

  1. Define the Data Contract: Ensure your contract class and its properties are decorated with [DataContractAttribute] and [DataMemberAttribute].
  2. Use CLR Interop to Access Reflection Classes: We’ll utilize System.Type and System.Reflection classes to access the metadata of the contract.
  3. Retrieve Custom Attribute Names: Dynamically extract the custom names defined in [DataMemberAttribute].

Example Code

Here’s a complete X++ code example to accomplish this:

public void contractToMap(Object _contract)
{
    Map                                     contractMap = new Map(Types::String, Types::String);
    System.Type                             clrType;
    System.Reflection.PropertyInfo[]        properties;
    System.Reflection.PropertyInfo          property;
    System.Object                           propertyValue;
    int                                     i, j;
    str                                     propertyName, dataMemberName;
    System.Object[]                         customAttributes;
    System.Type                             dataMemberAttributeType;
    System.Reflection.PropertyInfo          nameProperty;
    System.Attribute                        attribute;

    // Get the CLR type of the contract object
    clrType = _contract.GetType();

    // Get all public properties of the contract object
    properties = clrType.GetProperties();

    // Obtain the System.Type of DataMemberAttribute using GetType
    dataMemberAttributeType = System.Type::GetType('System.Runtime.Serialization.DataMemberAttribute, System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089');

    // Iterate over the properties
    for (i = 0; i < properties.get_Length(); i++)
    {
        property = properties.GetValue(i);

        // Get the name of the property
        propertyName = property.get_Name();
        dataMemberName = '';

        // Get custom attributes of the property
        customAttributes = property.GetCustomAttributes(true);

        // Iterate over custom attributes to find DataMemberAttribute
        for (j = 0; j < customAttributes.get_Length(); j++)
        {
            attribute = customAttributes.GetValue(j);
            if (attribute.GetType().Equals(dataMemberAttributeType))
            {
                // Get the 'Name' property of the DataMemberAttribute
                nameProperty = dataMemberAttributeType.GetProperty('Name');
                dataMemberName = nameProperty.GetValue(attribute, null);

                // If 'Name' is not set, it will be empty; in that case, use the property name
                if (dataMemberName == '')
                {
                    dataMemberName = propertyName;
                }
                break; // Exit the loop after finding the DataMemberAttribute
            }
        }

        // If DataMemberAttribute was not found, use the property name
        if (dataMemberName == '')
        {
            dataMemberName = propertyName;
        }

        // Get the value of the property
        propertyValue = property.GetValue(_contract, null);

        // Convert null values to empty strings to avoid errors
        if (propertyValue == null)
        {
            propertyValue = '';
        }

        // Insert the name and value into the map
        contractMap.insert(dataMemberName, any2Str(propertyValue));

        // Output for verification
        info(strFmt("Attribute: %1, Value: %2", dataMemberName, any2Str(propertyValue)));
    }

    // Now, contractMap contains all the attributes and their values
    info(strFmt("The map contains %1 elements.", contractMap.elements()));
}

Explanation

  • Obtain the Type of the Contract Object: The clrType variable is used to get the .NET type of the contract object.
  • Retrieve Properties: We fetch all public properties of the object using reflection.
  • Get DataMemberAttribute Type: Using System.Type::GetType(), we pass the full assembly-qualified name of DataMemberAttribute.
  • Iterate Over Properties: For each property, we check for custom attributes and specifically look for DataMemberAttribute.
  • Retrieve the Custom Name: If the attribute has a custom Name, we use it. Otherwise, we fall back to the property name.
  • Insert into a Map: We insert each attribute name and its corresponding value into a map.

Key Points

  1. Dynamic Attribute Access: This approach dynamically extracts the custom attribute names without hardcoding them.
  2. Use of .NET Reflection: By leveraging reflection, you can access properties and their metadata in a flexible way.
  3. Handling Missing or Null Values: The code handles cases where custom names are not specified or property values are null.

Benefits of This Approach

  • Maintainability: If new attributes are added to the contract class, there’s no need to update the iteration logic.
  • Flexibility: This solution adapts to changes in attribute names specified in [DataMemberAttribute].
  • Simplicity: Using reflection eliminates the need for manual enumeration or hardcoded attribute names.

Conclusion

By using .NET reflection within X++, you can dynamically access custom attribute names defined with [DataMemberAttribute] in a data contract class. This approach significantly enhances maintainability and flexibility, especially when working with large or frequently changing data contracts.

Final Tips

  • Error Handling: Consider adding try-catch blocks to handle potential errors gracefully.
  • Performance Considerations: Reflection can impact performance, so use it judiciously in performance-sensitive scenarios.
  • Version and Assembly Info: Ensure the assembly version and PublicKeyToken are correctly specified when using System.Type::GetType().

Example Use Case

Suppose you have a service that processes manufacturing orders. By dynamically accessing attribute names, you can automate the retrieval of data points like “Operation Number” and “Quantity” without needing to hardcode each attribute name. This flexibility allows for easy maintenance as data requirements evolve.

References

One response to “AX / D365FO – How to Dynamically Access Custom Attribute Names in Dynamics 365 FO Using X++”

  1. Martin Dráb Avatar
    Martin Dráb

    There is actually native support in F&O. For example, DictClass has getAllAttributes() method. You can use it like this:

    DictClass dictClass = new DictClass(classNum(AssetAcquisitionGERContract));
    Array attributes = dictClass.getAllAttributes();

    for (int i = 1; i <= attributes.lastIndex(); i++)
    {
    SysAttribute attribute = attributes.value(i);
    info(classId2Name(classIdGet(attribute)));
    }

    Like

Leave a comment