前提
微软在Platform Update 9之后引入了Chain of Command(CoC),通过支持像Public和Protected类型的拓展,来为技术顾问和编程人员减少过度分层(over layering)。
在PU15(Dynamic365的某一版本)中,在Form、Table和Class的CoC已经被实现,但在表单数据源(Form Data Source)和表单数据字段(Form data field)的部分还有所限制。
如不能对上述两种数据类型进行拓展。这些将在2018年10月的更新中得到实现(现已实装)。
代码
[Form]
public class ARBTestForm extend FormRun {
[DataSource]
class CustGroup {
public void initValue() {
super();
custGroup.PaymTermId = "Net10";
info(strFmt("Pre CoC: %1 set to %2", fieldId2PName(tableNum(CustGroup), fieldNum(CustGroup, PaymTermId)), custGroup.PaymTermId));
}
[DataField]
class CustGroup {
public void modified() {
super();
CustGroup.Name = "TEST";
warning(strFmt("Pre CoC: %1 set to %2", fieldId2PName(tableNum(CustGroup), fieldNum(CustGroup, Name)), CustGroup.Name));
}
}
}
[Control("Button")]
class ExecuteButton {
public void clicked() {
super();
}
public void execute() {
error("Post CoC: Operation executed by CoC.");
}
}
}
上述代码显示了一组简单的对于Form Control、Data Source和Data Field的实现。但也可发现他们是不可继承的,如果有更改的需求,没有CoC的话就只能进行复制一份然后编写重复代码这样的行为。
对此,Dynamics365 FO提供了一套解决方案
解决
CoC在Form和Data Foeld的语法层面上会与Table和Class的CoC有所不同。我们需要对每一个需要继承的对象进行继承(extend)。可能在最开始会有些不习惯和觉得冗长复杂,但习惯后会发现这是个不错的解决方案。
通过CoC,上述的代码将如此动作:
- 在ARMTestForm上创建一个新的CustGroup记录会调用(invoke)initValue方法,并将Payment Term Id设置为Net10,画面显示为Terms of Payment。并在画面以info级别打印信息:“Pre CoC:Terms of payment set to Net10”。
- 更改CustGroup记录将会触发CustGroup的modified方法,将CustGroup的Name属性设置为TEST,画面显示为Description。并在画面以Warning级别打印情报:“Pre CoC:Description set to TEST”。
- 当Execute按钮被点击时,由于在Clicked方法中只执行了super方法,所以没有任何动作。
在我们实际开始编写CoC代码前有几点需要注意: - class要被声明为final
- 在方法中必须要有next关键字
关于next关键字
next关键字类似一个super,它定义了你业务代码的执行逻辑,熟悉Java切片式编程的人可以把他类比于Spring中的joincut,在next之前定义的增强代码相当于@before的代码——父类对应代码的前置事件处理逻辑在此处执行,在next之后定义的可以理解为@after的代码——父类对应代码的后置事件处理逻辑在此处执行。
实现
让我们看以下三个类的实现来说明下CoC对Data Source、Data Field和Form Control的实现。
- DataSource Extension Class
[ExtensionOf(FormDataSourceStr(ARBTestForm, CustGroup))]
final class ARBFormCustGroup_Extension {
public void initValue() {
FormDataSource formDS = this;
CustGroup cust = formDS.cursor();
next initValue();
cust.PaymTermId = "Net30";
info(strFmt("Pre CoC: %1 set to %2", fieldId2PName(tableNum(CustGroup), fieldNum(CustGroup, PaymTermId)), custGroup.PaymTermId));
}
}
- Data source Field extension class
[ExtensionOf(FormDataFieldStr(ARBTestForm, CustGroup, CustGroup))]
final class ARBFormCustField_Extension {
public void modified() {
CustGroup custGroup;
FormDataSource ds;
FormDataObject df = any2Object(this) as FormDataObject;
next modified();
ds = df.datasource();
custGroup = ds.cursor();
custGroup.Name = strFmt("%1 with added description", CustGroup.Name);
warning(strFmt("Pre CoC: %1 set to %2", fieldId2PName(tableNum(CustGroup), fieldNum(CustGroup, Name)), CustGroup.Name));
}
}
- Button Control extension class
[ExtensionOf(FormControlStr(ARBTestForm, ExecuteButton))]
final class ARBFormExecuteButton_Extension {
public void clicked() {
CustGroup custGroup;
FormButtonControl fbc = any2Object(this) as FormButtonControl;
FormDataSource fds = fbc.formRun().dataSource(tableStr(CustGroup));
next clicked();
custGroup = fds.cursor();
if(custGroup.CustGroup == '10' || custGroup.CustGroup == '20') {
this.execute();
}
}
}
其行为如下:
- 在父类代码(称为父类并不准确,因为并没有通过extend关键字直接extend)中,在initValue方法中将PaymTermId字段设置为了Net10,通过CoC,将该字段设置为Net30,画面显示为Terms of Payment,并通过info方式显示信息。
- 在父类代码中,通过modified方法监听记录的更改操作。在CoC下,将Name字段的值设置为TEST
with added description,在画面显示为字段Description,并通过Warning显示信息。
- 在父类代码中,通过clicked方法来监听Execute按钮的点击操作,在CoC中,添加逻辑:如果该记录的CustGroup为10或20时才执行execute方法。并调用父类的execute方法,以error显示信息:After CoC: Operation executed through CoC。
由于execute方法在嵌套控件类型(nested control type, 此处为FormControl)的原始基本行为(original base behavior)中未被定义,不能直接通过execute来自定义行为。
上述FormControl的执行结果如下:
总结
- 像其他所有的CoC实现一样,next必须要被调用来保证父类方法的执行。
- 在现在的Visual Studion X++ Editor中,无法通过补全或者其他支持来获取可被包装方法的信息(如上文中clicked和execute的区别),需要查阅系统文档或查看方法签名方式来确定。
- 对于需要被包装的无法被包装的方法(如上文的execute),可以通过对control型方法的CoC定义。
- 无需通过再次编译来获取对要继承Form的CoC支持,新增extension->定义各个方法后就可在画面上直接确认。