Use the RunBase framework throughout Dynamics AX whenever you must execute a business transaction job. Extending the RunBase framework allows you to implement business operations that don’t have default support in the Dynamics AX application. The RunBase
framework supplies many features, including dialog boxes, query
windows, validation-before-execution windows, the progress bar,
client/server optimization, pack-unpack with versioning, and optional
scheduled batch execution at a given date and time.
1. Inheritance in the RunBase Framework
Classes that use the RunBase framework must inherit from either the RunBase class or the RunBaseBatch class. If the class extends RunBaseBatch, it can be enabled for scheduled execution in batch mode.
In a good inheritance model, each class has a
public construction mechanism, unless the class is abstract. If the
class doesn’t have to be initialized, use a static construct method.
Because X++ doesn’t support method name overloading, you should use a
static new method if the class must be initialized further upon instantiation. Static new methods have the following characteristics:
They are public and static.
Their names are prefixed with new.
They are named logically or with the arguments that they take. Examples include newInventTrans and newInventMovement.
They usually take nondefault parameters only.
They always return a valid object of the class type, instantiated and initialized, or throw an error.
Note
A class can have several new methods with different parameter profiles. The NumberSeq class is an example of a class with multiple new methods. |
The default constructor (the new method) should be protected to force users of the class to instantiate and initialize it with the static construct or new method. If new has some extra initialization logic that is always executed, you should place it in a separate init method.
2. Property Method Pattern
To allow other business operations to run your
new business operation, you might want to run it without presenting any
dialog boxes to the user. If you decide not to use dialog boxes, you
need an alternative to them to set the values of the necessary member
variables of your business operation class.
In Dynamics AX classes, member variables are
always protected. In other words, they can’t be accessed outside of the
class; they can be accessed only from within objects of the class or
its subclasses. To access member variables from outside the class, you
must write accessor methods. The accessor methods can get, set, or both
get and set member variable values. All accessor methods start with parm. In Dynamics AX, accessor methods are frequently referred to as parm methods.
The following is an example of what a method implementing the property method pattern could look like.
public NoYesId parmCreateServiceOrders(NoYesId _createServiceOrders =
createServiceOrders)
{
;
createServiceOrders = _createServiceOrders;
return createServiceOrders;
}
|
If you want the method to work only as a get method, change it to something such as this.
public NoYesId parmCreateServiceOrders()
{
;
return createServiceOrders;
}
|
And if you want the method to work only as a set method, change it to this.
public void parmCreateServiceOrders(NoYesId _createServiceOrders =
createServiceOrders)
{
;
createServiceOrders = _createServiceOrders;
}
|
When member variables contain huge amounts of
data (such as large containers or memo fields), the technique in the
following example is recommended. This technique determines whether the
parameter is changed. The disadvantage of using this technique in all
cases is the overhead of an additional method call.
public container parmCode(container _code = conNull())
{
;
if (!prmIsDefault(_code)
{
code = _code;
}
return code;
}
|
Tip
From the X++ editor window, you can access a template script to help you create parm
methods. Right-click the editor window, point to Scripts, point to
Template, point to Method, and then click Parm. A dialog box appears in
which you must enter the variable type and name of the member variable
that you want the parm method to give access to. You can also access the script by pressing Shift+F10 in the editor window and then selecting Scripts. |
3. Pack-Unpack Pattern
When you want to save the state of an object
with the option to reinstantiate the same object later, you must use the
pack-unpack pattern. The RunBase
framework requires that you implement this pattern to switch the class
between client and server (for client/server optimization) and to
present the user with a dialog box that states the choices made at the
last execution of the class. If your class extends the RunBaseBatch class, you also need to use the pack-unpack pattern for scheduled execution in batch mode.
The pattern consists of a pack method and an unpack method. These methods are used by the SysLastValue framework, which stores and retrieves user settings or usage data values that persist between processes.
Note
A
reinstantiated object is not the same object as the saved object. It is
a copy of the object with the same values as the packed and unpacked
member variables. |
pack and unpack Methods
The pack
method must be able to read the state of the object and return it in a
container. Reading the state of the object involves reading the values
of the variables needed to hydrate and dehydrate the object. Variables
used at execution time that are declared as member variables don’t have
to be included in the pack method. The
first entry in the container must be a version number that identifies
the version of the saved structure. The following code is an example of
the pack method.
container pack()
{
;
return [#CurrentVersion, #CurrentList];
}
|
Macros must be defined in the class declaration. CurrentList is a macro defined in the ClassDeclaration holding a list of the member variables to pack. If the variables in the CurrentList macro are changed, the version number should also be changed to allow safe and versioned unpacking. The unpack method can support unpacking previous versions of the class, as shown in the following example.
class InventCostClosing extends RunBaseBatch
{
#define.maxCommitCount(25)
// Parameters
TransDate transDate;
InventAdjustmentSpec specification;
NoYes prodJournal;
NoYes updateLedger;
NoYes cancelRecalculation;
NoYes runRecalculation;
FreeTxt freeTxt;
Integer maxIterations;
CostAmount minTransferValue;
InventAdjustmentType adjustmentType;
boolean collapseGroups;
...
#DEFINE.CurrentVersion(4)
#LOCALMACRO.CurrentList
TransDate,
Specification,
ProdJournal,
UpdateLedger,
FreeTxt,
MaxIterations,
MinTransferValue,
adjustmentType,
cancelRecalculation,
runRecalculation,
collapseGroups
#ENDMACRO
}
public boolean unpack(container packedClass)
{
#LOCALMACRO.Version1List
TransDate,
Specification,
ProdJournal,
UpdateLedger,
FreeTxt,
MaxIterations,
MinTransferValue,
adjustmentType,
del_minSettlePct,
del_minSettleValue
#ENDMACRO
#LOCALMACRO.Version2List
TransDate,
Specification,
ProdJournal,
UpdateLedger,
FreeTxt,
MaxIterations,
MinTransferValue,
adjustmentType,
del_minSettlePct,
del_minSettleValue,
cancelRecalculation,
runRecalculation,
collapseGroups
#ENDMACRO
Percent del_minSettlePct;
CostAmount del_minSettleValue;
boolean _ret;
Integer _version = conpeek(packedClass,1);
switch (_version)
{
case #CurrentVersion:
[_version, #CurrentList] = packedClass;
_ret = true;
break;
case 3:
// List has not changed, just the prodJournal must now always be updated
[_version, #CurrentList] = packedClass;
prodJournal = NoYes::Yes;
updateLedger = NoYes::Yes;
_ret = true;
break;
case 2:
[_version, #Version2List] = packedClass;
prodJournal = NoYes::Yes;
updateLedger = NoYes::Yes;
_ret = true;
break;
case 1:
[_version, #Version1List] = packedClass;
cancelRecalculation = NoYes::Yes;
runRecalculation = NoYes::No;
_ret = true;
break;
default:
_ret = false;
}
return _ret;
}
|
If any member variable
isn’t packable, the class can’t be packed and reinstantiated to the same
state. If any of the members are other classes, records, cursors, or
temporary tables, they must also be made packable. Other classes that
don’t extend RunBase can implement the pack and unpack methods by implementing the SysPackable interface.
When the object is reinstantiated, it must be possible to call the unpack method, which reads the saved state and reapplies the values of the member variables. The unpack method can reapply the correct set of member variables according to the saved version number, as shown in this example.
public boolean unpack(container _packedClass)
{
Version version = conpeek(_packedClass, 1);
;
switch (version)
{
case #CurrentVersion:
[version, #CurrentList] = _packedClass;
break;
default:
return false;
}
return true;
}
|
The unpack method returns a Boolean value that indicates whether the initialization succeeded.
As mentioned earlier in this section, the pack and unpack methods have three responsibilities:
Switching a RunBase-derived class between client and server.
Presenting the user with final choices made when the class was last executed.
Scheduling the execution of the class in batch mode.
In some scenarios, it is useful to execute specific logic depending on the context in which the pack or unpack method is called. You can use the isSwappingPrompt method on RunBase to detect whether the pack or unpack method is called in the context of switching between client and server. The isSwappingPrompt method returns true when called in this context. You can use the isInBatch method on RunBaseBatch to detect whether the unpack method is called in the context of executing the class in batch mode.