Chain of Command: The Modern Extension Model
Master CoC β extend F&O classes without modifying original code. Learn syntax, rules, overlayering vs extensions.
Chain of Command: The Modern Extension Model
CoC adds links to a chain without touching the original. Your code runs before/after the original method. next() passes control forward. Overlayering (modifying originals) is blocked β the extension model (CoC, event handlers, delegates) is the only supported customisation path.
Core Syntax
[ExtensionOf(classStr(SalesLineType))]
final class SalesLineType_Extension
{
public void validateWrite()
{
info("Before original");
next validateWrite();
info("After original");
}
}
With return values
[ExtensionOf(tableStr(SalesTable))]
final class SalesTable_Extension
{
public boolean validateField(FieldId _fieldId)
{
boolean isValid = next validateField(_fieldId);
if (_fieldId == fieldNum(SalesTable, CustAccount))
{
CustTable ct;
select firstOnly ct where ct.AccountNum == this.CustAccount;
if (ct.Blocked != CustVendorBlocked::No)
{
isValid = false;
error("Blocked customer");
}
}
return isValid;
}
}
ποΈ Vik: βEvery client project uses CoC. Zero changes to standard classes, clean upgrade path.β
Rules
| Rule | Allowed | Not Allowed |
|---|---|---|
| Members | Public/protected | Private = compile error |
| Class modifier | Must be final | Not abstract |
| Signature | Exact match required | Cannot change |
| next | Required β must be unconditional | No next inside if/while/after return |
| Targets | Classes, tables, forms, data entities | Private internals |
Overlayering vs Extensions
| Overlayering (Dead) | Extensions (CoC) | |
|---|---|---|
| Approach | Modify Microsoft code | Wrap without touching |
| Upgrades | Breaks on every update | Survives updates |
| ISV | Conflicts | Multiple extensions chain |
| Status | Blocked | Only supported way |
| Access | Everything | Public/protected |
CoC on Tables and Forms
[ExtensionOf(tableStr(CustTable))]
final class CustTable_Extension
{
public void insert()
{
if (!this.Name)
{
throw error("Customer name is required");
}
next insert();
info(strFmt("Customer %1 created", this.AccountNum));
}
}
[ExtensionOf(formDataSourceStr(SalesTable, SalesTable))]
final class SalesForm_DS_Ext
{
public void init()
{
next init();
this.query().dataSourceTable(tableNum(SalesTable))
.addRange(fieldNum(SalesTable, SalesStatus))
.value(queryValue(SalesStatus::Backorder));
}
}
Note: tableStr() for tables, formDataSourceStr(Form, DS) for form data sources.
Exam tip: next must be unconditional
The runtime requires next to always be called β you cannot put next inside an if statement, after a return, or inside a loop. If you need to prevent the original logic from executing, throw an exception instead of using return before next. The exam tests this: a return statement before next is a compile error.
CoC on validateWrite() without next. What happens?
Validation needed BEFORE insert. CoC structure?
Access private field from CoC extension?
Next up: Event Handlers & Delegates β the other extensibility pattern.