做过AX开发的同学们应该对runbase、runbasebatch、runbasereport这些类比较的熟悉,用它们来从用户收集数据、交互或者batch方式执行操作、并纪录用户的输入选项下次运行时初始化这些选项,为此需要重载pack、unpack、diaog、getfromdialog、run、cangobatch等函数。在AX2012中微软引入了新的SysOperation框架,用于替换runbase框架,微软提供了一份长达58页的白皮书介绍如何使用SysOperation框架,在这里下载http://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=29215,所用到的示例代码可以到这里下载http://blogs.msdn.com/b/aif/archive/2012/03/17/introduction-to-the-sysoperation-framework.aspx。本文就如何使用SysOperation框架做简单的探讨,先贴下示例代码的第一个例子,这样便于后续的说明。
class SysOpSampleBasicController extends SysOpSampleBaseController { } public ClassDescription caption() { return 'Basic SysOperation Sample'; } void new() { super(); this.parmClassName(classStr(SysOpSampleBasicController)); this.parmMethodName(methodStr(SysOpSampleBasicController, showTextInInfolog)); this.parmDialogCaption('Basic SysOperation Sample'); } public void showTextInInfolog(SysOpSampleBasicDataContract 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('SysOpSampleBasicController: %1, %2', data.parmNumber(), data.parmText())); } public static void main(Args args) { SysOpSampleBasicController operation; operation = new SysOpSampleBasicController(); operation.startOperation(); }
首先是SysOpSampleBasicController类,它继承于SysOpSampleBaseController,后者又继承于SysOperationServiceController,按照白皮书的说明,SysOperation框架的控制类应该直接派生于SysOperationServiceController类,但是目前AX2012版本的SysOperationServiceController类有些问题,所以有派生了SysOpSampleBaseController类来修复这些问题,在以后的版本中SysOperationServiceController会被修复,这里就把SysOpSampleBaseController当作控制类的最基类就行了。
可以看到控制类所要重载的函数少了很多,关键的地方一是在new()函数中parmClassName()指定控制类名称、parmMethodName()指定要运行的方法名称及parmDialogCaption()指定交互对话框的标题,另外一个关键的函数就是showTextInInfolog(),它被作为参数传入parmMethodName(),所以这个函数的名称是任意的,但是更改后记得做一次CIL增量编译,并清空有关user data,否则会得到方法找不到的错误提示。再来看showTextInInfolog()参数用到的SysOpSampleBasicDataContract类:
[DataContractAttribute] class SysOpSampleBasicDataContract { 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; }
它和运行SSRS报表收集报表参数的data contract类一样使用[DataContractAttribute]特性,两个parmxxx()方法也加入了一些特性指定在交互对话框上显示的标签名称、帮助信息及显示顺序。
运行只需要调用控制类的startOperation()就可以了,它会调用指定showTextInInfolog()方法。它会弹出对话框收集tex和number两个参数,可以放到batch方法去运行,这就是最简单的SysOperation试用方法,和runbase框架相比得到了很大的简化。
再来看第二个例子,它考虑的问题是如何对交互对话框用户输入的数据做lookup和验证,在runbase框架中我们需要在runbase的dialogPostRun中对dialogField注册重载函数,比如:
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); }
在SysOperation框架中首先对data contract类添加了SysOperationContractProcessingAttribute特性:
[DataContractAttribute, SysOperationContractProcessingAttribute(classStr(SysOpSampleSimpleUserInterfaceBuilder))] class SysOpSampleSimpleDataContract { str text; int number; }
由这个特性指定一个自定义的交互对话框构建器类,没有这个特性,会使用系统默认的SysOperationAutomaticUIBuilder构建器,来看看这个自定义的UI构建器是如何做到对dialogField的lookup和validate的:
class SysOpSampleSimpleUserInterfaceBuilder extends SysOperationAutomaticUIBuilder { #define.lookupAlways(2) DialogField numberField; DialogField textField; } 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 boolean numberFieldValidate(FormIntControl _control) { if (_control.value() < 0) { error('Please type a number >= 0'); return false; } return true; } 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(); }
其实要做的工作和runbase框架差不多,也是要对dialogfield注册重载函数,SysOperation框架的处理方式是把这部分处理独立到单独的UI构建器,使得逻辑上比较清晰,注意postbuild方法中是如何得到交互对话框上的两个dialogfield控件的。SysOperation框架还支持使用自定义的form来构建UI,需要重载SysOperationController.templateForm()指定一个form。
showTextInInfolog()函数也从控制类中脱离出来被放到新的service类中:
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())); }
相应的,控制类的new()需要指定服务类的showTextInInfolog()为实际运行方法:
void new() { super(); this.parmClassName(classStr(SysOpSampleSimpleService)); this.parmMethodName(methodStr(SysOpSampleSimpleService, showTextInInfolog)); }
这样做使得控制类变得更薄,整体上使用类来封装和组织分离各部分代码逻辑,也是为了使整体结构变得更加清晰。
第三个例子演示如何向通过控制类的parmExecutionMode()传入SysOperationExecutionMode枚举类型参数来实现不同的运行模式。主要关注的是Reliable Asynchronous方式,它等同于把操作放到batch server去运行,但不同点是在任务完成后从batch jobs列表中自动删除,仅保留运行历史纪录。这个例子稍显复杂,单靠文字不是很能说明清楚,这里就略过了。
最后一个例子更加详尽的演示了如何使用SysOperation做异步操作,包括如何在多个处理器上同时运行任务、监测异步操作错误、使用Alert机制提示等,从略省过。所以这里只是看看最简单的SysOperation是如何实现的,有兴趣的同学们好好看看这份白皮书吧。