7.2 Attributes & Reflection
Key Takeaways
- Attributes in X++ are declarative classes derived from SysAttribute that annotate classes, methods, and tables with metadata evaluated at compile-time and runtime.
- The SysOperation framework relies on [DataContractAttribute] to mark parameter storage classes and [DataMemberAttribute] to expose serialized accessor methods across asynchronous and batch tiers.
- Custom and standard service operations exposed via SOAP or JSON REST endpoints must be decorated with [SysEntryPointAttribute(true)] to enforce role-based security privilege checks prior to execution.
- Extensibility controls include [Hookable(false)] to block pre/post method events and Chain of Command wrapping, [ExtensionOf] to bind augmentation classes, and [SysObsoleteAttribute] to signal API deprecation.
- Reflection in modern X++ utilizes Dictionary reflection classes—primarily DictClass, DictMethod, and SysDictType—to discover attributes at runtime and dynamically invoke methods via callObject().
7.2 Attributes & Reflection
Quick Answer: Attributes in X++ are metadata classes inheriting from
SysAttributethat annotate classes, methods, and tables. In the SysOperation framework,[DataContractAttribute]designates parameter serialization containers, while[DataMemberAttribute('Name')]marks getter/setter parameter methods. External services mandate[SysEntryPointAttribute(true)]to trigger Application Object Server (AOS) role-based authorization. Extensibility boundaries are governed by[Hookable(false)](which prohibits both pre/post events and Chain of Command wrapping),[ExtensionOf](which binds CoC augmentation classes), and[SysObsoleteAttribute](which flags deprecated APIs with warnings or errors). Reflection APIs—principallyDictClass,DictMethod, andSysDictType—inspect metadata dynamically at runtime, query attributes viaisAttributeDefined()andgetAttribute(), and invoke dynamic methods viaDictClass.callObject().
1. Attribute-Driven Architecture in X++
Attributes provide a structured mechanism for attaching declarative metadata to X++ code elements without altering their core operational logic. When code is compiled, attribute declarations are emitted directly into the assembly metadata of the generated .NET assemblies.
The Core Attribute Lifecycle
- Design Time: Developers declare attributes on classes, tables, forms, or methods.
- Compile Time: The X++ compiler validates attribute targets, serializes attribute constructor arguments into assembly metadata, and enforces compiler constraints (e.g., verifying
[ExtensionOf]syntax). - Runtime: Framework engines (such as the SysOperation framework, FormRun engine, or integration services) use reflection to discover attributes and alter processing pipelines.
2. Designing Custom Attributes with SysAttribute
To create a custom attribute in X++, a developer declares a class that directly extends the system base class SysAttribute.
Implementation Rules for Custom Attributes
- Must inherit from
SysAttribute. - Typically ends with the suffix
Attribute(e.g.,PaymentIntegrationAttribute). - Can declare instance variables and a constructor to receive declarative arguments.
- Stored state should be exposed through public getter methods.
/// <summary>
/// Custom attribute marking integration handler classes with protocol metadata.
/// </summary>
public final class ExportFormatAttribute extends SysAttribute
{
private str formatName;
private boolean supportsCompression;
public void new(str _formatName, boolean _supportsCompression = false)
{
super();
this.formatName = _formatName;
this.supportsCompression = _supportsCompression;
}
public str parmFormatName()
{
return formatName;
}
public boolean parmSupportsCompression()
{
return supportsCompression;
}
}
Decorating Target Classes
Once compiled, the custom attribute can annotate any class or method:
[ExportFormatAttribute("ISO20022_XML", true)]
public class ISO20022PaymentExporter extends PaymentExporterBase
{
// Format-specific export implementation
}
3. Key Platform Attributes: Deep Architectural Breakdown
Dynamics 365 Finance and Operations relies heavily on standardized platform attributes to govern serialization, security, extensibility, and lifecycle management.
Comprehensive Platform Attribute Matrix
| Attribute Name | Target Elements | Primary Functional Purpose | Runtime / Compiler Behavior |
|---|---|---|---|
[DataContractAttribute] | Class | Defines a serialization contract for SysOperation or integration services. | Enables CLR XML/JSON serialization between tiers and batch storage. |
[DataMemberAttribute] | Method | Exposes a parameter getter/setter method inside a Data Contract. | Serializes individual member values; accepts optional alias string. |
[SysEntryPointAttribute] | Method | Enforces role-based security authorization on service operations. | When true, verifies calling user permissions; when false, bypasses checks. |
[Hookable(false)] | Method / Class | Completely closes an element to external extensibility hooks. | Blocks both Pre/Post event subscriptions and Chain of Command wrapping. |
[ExtensionOf] | Class | Designates an augmentation class for Chain of Command (CoC). | Compiler links class to target element; requires final class ..._Extension. |
[SysObsoleteAttribute] | Class / Method | Marks obsolete code and guides developers to replacement APIs. | Generates compiler warnings (if isError = false) or errors (if true). |
[ExportMetadataAttribute] | Class | Attaches key-value metadata to MEF (Managed Extensibility Framework) plugins. | Used by kernel plugin engines for zero-compilation pluggable extensions. |
Deep Dive: [DataContractAttribute] and [DataMemberAttribute]
In the SysOperation framework, parameters cannot be passed as raw table buffers or loose primitives across tier boundaries. Instead, parameters are encapsulated inside a Data Contract class decorated with [DataContractAttribute].
- Each member property is exposed via a public
parmmethod decorated with[DataMemberAttribute('Name')]. - The runtime serializes the contract into an XML pack string stored in
SysOperationDataContractInfoinside the batch tables (BatchJob,Batch).
[DataContractAttribute]
public class CustInvoiceBatchContract
{
private CustAccount customerAccount;
private TransDate fromDate;
[DataMemberAttribute('CustAccount')]
public CustAccount parmCustAccount(CustAccount _customerAccount = customerAccount)
{
customerAccount = _customerAccount;
return customerAccount;
}
[DataMemberAttribute('FromDate')]
public TransDate parmFromDate(TransDate _fromDate = fromDate)
{
fromDate = _fromDate;
return fromDate;
}
}
Deep Dive: [SysEntryPointAttribute(true)]
When exposing a custom service in Dynamics 365 (SOAP or JSON REST), the service operation method must be decorated with [SysEntryPointAttribute]:
[SysEntryPointAttribute(true)]: The Application Object Server (AOS) inspects the user's security privileges before allowing execution. If the user lacks access to the entry point menu item or service privilege, anAccessDeniedExceptionis thrown.[SysEntryPointAttribute(false)]: Disables automated authorization checks. Execution proceeds without entry-point security validation. This should be used strictly for internal diagnostics or anonymous utility endpoints.
Deep Dive: [Hookable(true/false)]
By default, all public and protected methods in X++ are hookable.
[Hookable(false)]: Explicitly prevents developers from creating Pre/Post event handlers and prevents wrapping the method via Chain of Command (CoC).- Why Microsoft uses
[Hookable(false)]: Applied to critical transactional or kernel algorithms (such as core ledger posting loops or cryptographic hashing) where external interference could corrupt transactional consistency or bypass security.
Deep Dive: [SysObsoleteAttribute]
When refactoring or deprecating APIs, developers use [SysObsoleteAttribute(str _message, boolean _isError)]:
_message: Explanation of the deprecation and reference to the replacement API._isError = false: Generates a compiler warning during build._isError = true: Generates a fatal compilation error, breaking any build that attempts to call the obsolete method.
4. Reflection APIs in X++: DictClass, DictMethod & SysDictType
Reflection enables X++ programs to inspect application metadata dynamically at runtime without knowing type definitions at compile time. Dynamics 365 provides dedicated Dictionary classes.
Core Reflection Classes
DictClass: Inspects class metadata. Allows querying superclasses, implemented interfaces, declared methods, custom attributes, and provides dynamic object instantiation and method execution.DictMethod: Inspects method metadata. Retrieves parameter counts, parameter types, return types, and method visibility.SysDictType: Inspects Extended Data Types (EDTs) and primitive types. Retrieves string length, decimal precision, labels, and underlying base types.DictTable: Inspects table metadata, fields, indexes, and relations.
5. Dynamic Method Invocation and Attribute Querying
Reflection is widely used in extensible enterprise frameworks to dynamically discover and invoke classes based on configuration records.
Inspecting Attributes at Runtime
public static boolean hasExportFormatAttribute(ClassName _className, str _expectedFormat)
{
ClassId classId = className2Id(_className);
if (!classId)
{
return false;
}
DictClass dictClass = new DictClass(classId);
// Check if the custom attribute is defined on the class
if (dictClass.isAttributeDefined(classStr(ExportFormatAttribute)))
{
// Retrieve the instantiated attribute object
ExportFormatAttribute attr = dictClass.getAttribute(classStr(ExportFormatAttribute)) as ExportFormatAttribute;
if (attr && attr.parmFormatName() == _expectedFormat)
{
return true;
}
}
return false;
}
Dynamic Instantiation and Invocation via DictClass
public static str executeDynamicProcessor(ClassName _className, str _payload)
{
ClassId classId = className2Id(_className);
DictClass dictClass = new DictClass(classId);
if (!dictClass)
{
throw error(strFmt("Class %1 does not exist.", _className));
}
// Dynamically instantiate the object via makeObject
Object processorInstance = dictClass.makeObject();
// Verify method existence
if (dictClass.findObjectMethodObject(methodStr(PaymentExporterBase, processPayload)))
{
// Dynamically invoke method passing parameters via container
return dictClass.callObject(methodStr(PaymentExporterBase, processPayload), processorInstance, _payload);
}
throw error("Method not implemented on target class.");
}
6. Scenario Walk-Through: Dynamic Plugin Discovery Framework
Scenario Description
An enterprise shipping portal must dynamically discover and execute carrier tracking plugins (FedEx, UPS, DHL). New carrier plugins must be deployable by ISVs without altering the core shipping engine codebase. The system must query all classes decorated with [CarrierPluginAttribute], find the matching carrier ID, instantiate the class dynamically, and invoke its tracking endpoint.
Implementation Walk-Through
- Declare the Custom Attribute (
CarrierPluginAttribute):- Extend
SysAttributewith parameterCarrierCode carrierCode.
- Extend
- Annotate Concrete Carrier Plugins:
- Decorate
FedExCarrierServicewith[CarrierPluginAttribute("FEDEX")]. - Decorate
UPSCarrierServicewith[CarrierPluginAttribute("UPS")].
- Decorate
- Scan Metadata via
SysExtensionAppClassCache/DictClass:- Query candidate classes implementing interface
ICarrierService. - For each class, inspect
dictClass.getAttribute(classStr(CarrierPluginAttribute)).
- Query candidate classes implementing interface
- Match and Execute:
- When the user tracks a FedEx parcel, match
"FEDEX". - Instantiate via
dictClass.makeObject()and cast toICarrierService. - Invoke
carrierService.trackShipment(trackingNum).
- When the user tracks a FedEx parcel, match
7. Real-World Exam Traps: Attributes & Reflection
[!WARNING] Exam Trap 1: Setting
[SysEntryPointAttribute(false)]on Production Custom Services An exam scenario describes a custom REST service that external users can execute even without having assigned security privileges. The cause is that the service operation was decorated with[SysEntryPointAttribute(false)]. Setting this property tofalsedisables automated authorization. The correct production configuration is[SysEntryPointAttribute(true)].
[!WARNING] Exam Trap 2: Omitting
[DataMemberAttribute]on Contract Methods A developer creates a Data Contract class with[DataContractAttribute], but when the batch job executes, parameter values reset to blank or null. The developer forgot to decorate theparmgetter/setter methods with[DataMemberAttribute]. Without[DataMemberAttribute], the serialization engine ignores the property entirely.
[!WARNING] Exam Trap 3: Believing
[Hookable(false)]Only Blocks Pre/Post Events An exam question asks how to prevent external developers from wrapping a method using Chain of Command (CoC) when the method cannot be markedfinal. The correct solution is decorating the method with[Hookable(false)].[Hookable(false)]blocks both Pre/Post event subscriptions and Chain of Command wrapping.
[!WARNING] Exam Trap 4: Passing Incorrect Argument Types to
DictClass.callObject()When callingDictClass.callObject(), arguments passed must match the method signature exactly. If arguments are passed incorrectly or the method is static instead of an instance method (callStaticmust be used for static methods), a fatal CLR reflection exception is thrown.
A developer is creating a custom JSON REST service in Dynamics 365 Finance and Operations. The business requirement dictates that the Application Object Server (AOS) must automatically validate whether the calling user possesses the required security privileges for the service operation before executing the method. Which attribute must annotate the service operation method?
A developer creates a batch processing parameter class for the SysOperation framework. When the batch job runs in the background, the parameters entered by the user in the dialog are lost and appear as null inside the service execution method. What is the most likely cause of this behavior?
An ISV solution architect wants to prevent third-party developers from subscribing to Pre/Post method events or wrapping a critical financial calculation method with Chain of Command (CoC), while still keeping the method public for external invocation. How should the architect configure the method?
A developer is writing a dynamic framework in X++ that needs to inspect whether an arbitrary class has been annotated with a specific custom attribute named PaymentProcessorAttribute, and if so, retrieve an instance of the attribute to inspect its configuration properties. Which reflection approach correctly accomplishes this?