Description:- SysOperation is a framework in Microsoft Dynamics® AX 2012 that
allows application logic to be written in a way that supports running
operations interactively or via the Microsoft Dynamics AX batch server. The
framework provides capabilities that are very similar to the RunBase framework
that came before it. The batch framework has very specific requirements for
defining operations:
1. The operation must support
parameter serialization so that its parameters can be saved to the batch table.
2. The operation must have a way to
display a user interface that can be launched from the batch job configuration
user interface.
3.
The
operation must implement the interfaces needed for integration with the batch
server runtime.
The RunBase framework defines
coding patterns that implement these requirements. The SysOperation framework
provides base implementations for many of the patterns defined by the RunBase
framework.
SysOperation and RunBase are frameworks geared toward building
operations that can run via the batch server or interactively. In order for an
operation to run via the batch server, it must meet these requirements:
1. It must support parameter
serialization via the SysPackable interface.
2. It must support the standard run
() method defined in the BatchRunable interface.
3. It must support the batch server
integration methods found in the Batchable interface.
4.
It must
provide a mechanism to show input parameters with a user interface.
Currently in Microsoft Dynamics AX, all operations that must run
via the batch server must derive from either the SysOperationController or
the RunBaseBatch base class.
The following two samples illustrate the basic capabilities
provided by the two frameworks:
1.
SysOpSampleSimpleRunbaseBatch
2.
SysOpSampleSimpleController
The simplest operation based on
the
RunBaseBatch base class has to implement 12 overrides. The purpose
of this sample is simply to compare the RunBase and SysOperation frameworks.
For full details of the RunBase framework, see the following article on MSDN:
RunBaseBatch FrameWork
There are four classes: the controller, the
service operation, the data contract, and the user interface builder.
let’s Start to Create Class in Ax Classes node and Name it “SysOpSampleSimpleRunbaseBatch”.
ClassDeclaration
- Derives from RunBaseBatch.
- Declares variables for operation input parameters.
- Declares variables for dialog box controls.
- Declares a macro defining a list of variables that need to be
serialized.
class SysOpSampleSimpleRunbaseBatch extends RunBaseBatch
{
str
text;
int
number;
DialogRunbase dialog;
DialogField numberField;
DialogField textField;
#define.lookupAlways(2)
#define.CurrentVersion(1)
#LOCALMACRO.CurrentList
text,
number
#ENDMACRO
}
protected Object dialog()
{
dialog = super();
textField = dialog.addFieldValue(IdentifierStr(Description255), text, 'Text Property','Type
some text here');
textField.lookupButton(#lookupAlways);
numberField = dialog.addFieldValue(IdentifierStr(Counter), text, 'Number Property','Type
some number here');
return
dialog;
}
public void
dialogPostRun(DialogRunbase _dialog)
{
FormControl control;
super(_dialog);
// register
overrides for form control events
numberField.registerOverrideMethod(methodstr(FormIntControl, validate), methodstr(SysOpSampleSimpleRunbaseBatch,
numberFieldValidate), this);
textField.registerOverrideMethod(methodstr(FormStringControl, lookup), methodstr(SysOpSampleSimpleRunbaseBatch,
textFieldLookup), this);
}
public boolean
getFromDialog()
{
text = textField.value();
number = numberField.value();
return
super();
}
public boolean
numberFieldValidate(FormIntControl _control)
{
if
(_control.value() < 0)
{
error('Please
type a number >= 0');
return
false;
}
return
true;
}
public container
pack()
{
return
[#CurrentVersion, #CurrentList];
}
public int
parmNumber(int _number = number)
{
number = _number;
return
number;
}
public str
parmText(str _text = text)
{
text = _text;
return
text;
}
protected void
putToDialog()
{
super();
textField.value(text);
numberField.value(number);
}
public void
run()
{
// marshall
call to server
SysOpSampleSimpleRunbaseBatch::showTextInInfolog(this.pack());
}
public void textFieldLookup(FormStringControl _control)
{
FormStringControl companyControl;
SysTableLookup tableLookup;
Query query = new Query();
companyControl = _control;
tableLookup =
SysTableLookup::newParameters(tablenum(DataArea),companyControl);
tableLookup.addLookupfield(fieldnum(DataArea,Id),true);
tableLookup.addLookupfield(fieldnum(DataArea,Name),false);
query.addDataSource(tablenum(DataArea));
tableLookup.parmQuery(query);
tableLookup.performFormLookup();
}
public boolean
unpack(container packedClass)
{
Integer version = conPeek(packedClass,1);
switch
(version)
{
case
#CurrentVersion:
[version,#CurrentList] =
packedClass;
break;
default:
return
false;
}
return
true;
}
public static
ClassDescription description()
{
return
'Intermediate RunBaseBatch Sample';
}
public static
void main(Args args)
{
SysOpSampleSimpleRunbaseBatch operation;
operation = new
SysOpSampleSimpleRunbaseBatch();
if
(operation.prompt())
{
operation.run();
}
}
private static
server void
showTextInInfolog(container
packedRunBase)
{
SysOpSampleSimpleRunbaseBatch thisClass;
// If not a
CLR session then marshall over. If already in a CLR session
// then
execute the logic for the operation
if
(!xSession::isCLRSession())
{
new
XppILExecutePermission().assert();
SysDictClass::invokeStaticMethodIL(classStr(SysOpSampleSimpleRunbaseBatch),
staticMethodStr(SysOpSampleSimpleRunbaseBatch,
showTextInInfolog),
packedRunBase);
// exit
call executed in CLR session.
return;
}
thisClass = new
SysOpSampleSimpleRunbaseBatch();
if
(!thisClass.unpack(packedRunBase))
{
throw
AifFault::fault('SysOpSampleSimpleRunbaseBatch
unpack error', 'unpackError');
}
if
(xSession::isCLRSession())
{
info('Running
in a CLR session.');
}
else
{
info('Running
in an interpreter session.');
if
(isRunningOnServer())
{
info('Running
on the AOS.');
}
else
{
info('Running
on the Client.');
}
}
info(strFmt('SysOpSampleSimpleRunbaseBatch: %1, %2',
thisClass.parmNumber(), thisClass.parmText()));
}
Create
Class in Classes node and Name it SysOpSampleSimpleDataContract.
[DataContractAttribute,
SysOperationContractProcessingAttribute(classStr(SysOpSampleSimpleUserInterfaceBuilder))]
class SysOpSampleSimpleDataContract
{
str
text;
int
number;
}
[DataMemberAttribute,
SysOperationLabelAttribute('Number Property'),
SysOperationHelpTextAttribute('Type some number >= 0'),
SysOperationDisplayOrderAttribute('2')]
public int
parmNumber(int _number = number)
{
number = _number;
return
number;
}
[DataMemberAttribute,
SysOperationLabelAttribute('Text Property'),
SysOperationHelpTextAttribute('Type some text'),
SysOperationDisplayOrderAttribute('1')]
public Description255 parmText(str _text = text)
{
text = _text;
return
text;
}
Create
Class in Classes node and Name it SysOpSampleSimpleController.
class SysOpSampleSimpleController extends SysOpSampleBaseController
{
}
public ClassDescription caption()
{
return
'Intermediate SysOperation Sample';
}
void new()
{
super();
this.parmClassName(classStr(SysOpSampleSimpleService));
this.parmMethodName(methodStr(SysOpSampleSimpleService,
showTextInInfolog));
}
public static
void main(Args args)
{
SysOpSampleSimpleController operation;
operation = new
SysOpSampleSimpleController();
operation.startOperation();
}
Create
Class in Classes node and Name it SysOpSampleSimpleService.
class SysOpSampleSimpleService extends SysOperationServiceBase
{
}
public void
showTextInInfolog(SysOpSampleSimpleDataContract data)
{
if
(xSession::isCLRSession())
{
info('Running
in a CLR session.');
}
else
{
info('Running
in an interpreter session.');
if
(isRunningOnServer())
{
info('Running
on the AOS.');
}
else
{
info('Running
on the Client.');
}
}
info(strFmt('SysOpSampleSimpleService: %1, %2',
data.parmNumber(), data.parmText()));
}
Create
Class in Classes node and Name it SysOpSampleSimpleUserInterfaceBuilder.
class SysOpSampleSimpleUserInterfaceBuilder extends SysOperationAutomaticUIBuilder
{
#define.lookupAlways(2)
DialogField numberField;
DialogField textField;
}
public boolean
numberFieldValidate(FormIntControl _control)
{
if
(_control.value() < 0)
{
error('Please
type a number >= 0');
return
false;
}
return
true;
}
public void
postBuild()
{
super();
// get
references to dialog controls after creation
numberField =
this.bindInfo().getDialogField(this.dataContractObject(), methodStr(SysOpSampleSimpleDataContract,
parmNumber));
textField =
this.bindInfo().getDialogField(this.dataContractObject(), methodStr(SysOpSampleSimpleDataContract,
parmText));
// change text
field metadata to add lookup
textField.lookupButton(#lookupAlways);
}
public void
postRun()
{
super();
// register
overrides for form control events
numberField.registerOverrideMethod(methodstr(FormIntControl, validate), methodstr(SysOpSampleSimpleUserInterfaceBuilder,
numberFieldValidate), this);
textField.registerOverrideMethod(methodstr(FormStringControl, lookup), methodstr(SysOpSampleSimpleUserInterfaceBuilder,
textFieldLookup), this);
}
public void textFieldLookup(FormStringControl _control)
{
FormStringControl companyControl;
SysTableLookup tableLookup;
Query
query = new Query();
companyControl = _control;
tableLookup =
SysTableLookup::newParameters(tablenum(DataArea),companyControl);
tableLookup.addLookupfield(fieldnum(DataArea,Id),true);
tableLookup.addLookupfield(fieldnum(DataArea,Name),false);
query.addDataSource(tablenum(DataArea));
tableLookup.parmQuery(query);
tableLookup.performFormLookup();
}
The service and data contract classes define the operation. The
derived controller class provides the main entry point and overrides the new()
method to associate the operation classes with the controller. The base
controller reflects on the operation and constructs metadata classes that
define the operation. The base class SysOperationAutomaticUIBuilder uses
the metadata derived from the operation to create the user interface. In the
sample, there is a derived user interface builder called SysOpSampleSimpleUserInterfaceBuilder.
This overrides the postBuild() and postRun() overrides on the
base builder to subscribe to form control events related to validation and
lookup.
The system uses SysOperationContractProcessingAttribute to
associate the custom user interface builder with the data contract.
[DataContractAttribute,
SysOperationContractProcessingAttribute(classStr(SysOpSampleSimpleUserInterfaceBuilder))]
class SysOpSampleSimpleDataContract
{
str text;
int number;
}
If this attribute is not present, the default builder, SysOperationAutomaticUIBuilder,
is used. As an experiment, comment out the attribute in the preceding code, and
then run the operation to see the differences.
The postBuild() override in the custom user interface
builder is where the form control metadata needs to be modified before the
controls are instantiated. The framework maintains an association between
controls and data contracts in a map that can be accessed via the this.bindInfo()
method. The map is keyed by the name of the property in the data contract.
public void postBuild()
{
super();
// get references to dialog
controls after creation
numberField =
this.bindInfo().getDialogField(this.dataContractObject(),
methodStr(SysOpSampleSimpleDataContract,
parmNumber));
textField =
this.bindInfo().getDialogField(this.dataContractObject(),
methodStr(SysOpSampleSimpleDataContract,
parmText));
// change text field
metadata to add lookup
textField.lookupButton(#lookupAlways);
}
The postRun() override
in the custom user interface builder is where the form control events are
subscribed to. The subscriptions must be added to the controls after they have
been instantiated.
public void postRun()
{
super(); // register
overrides for form control events
numberField.registerOverrideMethod(methodstr(FormIntControl,
validate),
methodstr(SysOpSampleSimpleUserInterfaceBuilder,
numberFieldValidate), this);
textField.registerOverrideMethod(methodstr(FormStringControl,
lookup),
methodstr(SysOpSampleSimpleUserInterfaceBuilder,
textFieldLookup), this);
}
The registerOverRideMethod method
on the controls is a run-time equivalent to the control overrides used in
normal forms. If you use an override method in a standard Microsoft® MorphX®
form, you can use the same method override in a dynamic form by using this
mechanism. Note that both the RunBase and SysOperation frameworks allow the use
of modeled forms as the operation user interface.
The SysOperation framework
provides the override SysOperationController.templateForm() for that
purpose, however, this topic is outside the scope of this white paper.
The samples in this section
show how the user interface for the operation can use many of the same features
that are available in the normal form programming model. Control overrides fire
run-time events that can be subscribed to. The SysOperation version of the
sample shows how the different aspects of the operation can be factored into
separate classes.
To show that everything is
possible with code, the RunBase sample is modified so that it marshals its
interactive execution into a CLR session, in the same way that the SysOperation
framework does. This illustrates the design principle that drove the SysOperation
framework: move as much of the boilerplate code as possible into the base
classes.
private static server
void showTextInInfolog(container
packedRunBase)
{
SysOpSampleSimpleRunbaseBatch
thisClass;
// If not in a CLR
session then marshal over. If already in a CLR session
// then execute the
logic for the operation
if (!xSession::isCLRSession())
{
new XppILExecutePermission().assert();
SysDictClass::invokeStaticMethodIL (classStr
(SysOpSampleSimpleRunbaseBatch),
StaticMethodStr (SysOpSampleSimpleRunbaseBatch,
showTextInInfolog),
packedRunBase);
// exit call executed in CLR session.
return;
}
thisClass = new SysOpSampleSimpleRunbaseBatch();
if (!thisClass.unpack(packedRunBase))
{
throw AifFault::fault('SysOpSampleSimpleRunbaseBatch
unpack error', 'unpackError');
}
if (xSession::isCLRSession())
{
info('Running in a CLR session.');
}
else
{
info('Running in an interpreter session.');
if (isRunningOnServer())
{
info('Running on the AOS.');
}
else
{
info('Running on the Client.');
}
}
info(strFmt('SysOpSampleSimpleRunbaseBatch:
%1, %2',
thisClass.parmNumber(),
thisClass.parmText()));
}
Now Generate CIL and run your Controller Class.