Method 开发与事件上下文
Method 是 Aras Innovator 中承载自定义业务逻辑的 Item。它可以作为 Item Action、通用方法、服务器事件或客户端事件运行。真正决定代码含义的,不只是语言,而是调用位置、上下文 Item、事务阶段和调用者权限。
适用范围
- Method 与 IOM 的基本模型跨版本较稳定。
- 本文的服务器事件语义以 Aras Innovator 35 Programmer's Guide 为主要基线。
- 用户资料中的
document.thisItem、relTabbar、iframe、grid.items_Experimental等属于 Classic Client 或私有实现;不能当成跨版本公共 API。
先判断应该写在哪一层
| 需求 | 首选位置 | 原因 |
|---|---|---|
| 显示/隐藏字段、友好提示 | 客户端或响应式表单配置 | 直接改善交互 |
| 防止非法数据保存 | 服务端事件 | 不能被其他客户端或 API 绕过 |
| 数据查询与业务计算 | 服务端 Method | 权限、事务与错误返回一致 |
| 外部系统集成 | IOM / REST 调用 | 与网页生命周期解耦 |
| 页面只读 | UI 配置 + 服务端权限 | UI 只读本身不是访问控制 |
一条实用原则
客户端校验负责“尽早告诉用户”,服务端校验负责“最终保证数据”。任何会影响数据正确性或授权的规则,都不能只写在 JavaScript 中。
Method 的几种调用模型
先用调用位置判断 this,不要把所有 JavaScript Method 都写成同一种上下文:
| Classic Client 调用位置 | this 的常见含义 | 当前 Item 的常见入口 |
|---|---|---|
| Item Action | 被操作的 Item | this |
| Form / Grid 事件 | 浏览器 document | document.thisItem 或该事件公开参数 |
| Field 事件 | 字段控件 | 从字段/表单上下文取得 |
| Relationship Grid | 关系网格上下文 | 常见旧实现为 parent.thisItem,需按版本核验 |
这是 R29 Programmer's Guide 对 Classic Client 的上下文模型。Responsive Form 或新客户端扩展点不能直接套用这张表。
Item Action
Item Action 把 Method 暴露为某个 Item 的动作。上下文通常是被操作的 Item,但仍应校验类型:
Innovator inn = this.getInnovator();
if (this.getType() != "Part")
{
return inn.newError("该方法只能用于 Part。");
}
return inn.newResult(this.getProperty("item_number", ""));Action 类 Method 应返回一个 Item,例如 inn.newResult(...)、inn.newError(...) 或查询结果,而不是依赖控制台输出。
通用 Method
通用 Method 可通过 applyMethod 调用。调用方传入的参数会出现在 context 中,方法必须把它们当作不可信输入:
Innovator inn = this.getInnovator();
string partId = this.getProperty("part_id", "");
if (partId.Length != 32)
{
return inn.newError("part_id 必须是 32 位 Item ID。");
}
Item query = inn.newItem("Part", "get");
query.setID(partId);
query.setAttribute("select", "id,item_number,name");
return query.apply();调用端:
Item result = inn.applyMethod(
"acme_GetPartSummary",
"<part_id>PART_ID</part_id>"
);真实参数若含 &、<、> 等字符,不要手工拼 XML;使用 IOM 构造参数 Item,或先进行正确的 XML 编码。
服务器事件
服务器事件拦截内置 action 的处理过程。常见族群包括 OnBefore*、On* 和 OnAfter*。
| 阶段 | 典型职责 | 是否可把它称为“提交后” |
|---|---|---|
OnBefore* | 校验、补充请求数据 | 否 |
On* | 替换标准处理 | 否;使用需非常谨慎 |
OnAfter* | 基于已处理结果做同事务后续工作 | 不能 |
OnAfter* 仍在事务语义中
官方文档明确说明:如果 OnAfter 事件返回错误,整个 transaction 会回滚。因此不要把 OnAfterAdd 或 OnAfterUpdate 写成“数据库已经提交”的钩子,也不要在这里盲目调用不可回滚的外部副作用。
客户端事件
客户端 Method 使用 JavaScript,可绑定 Form、Field、Grid 或 ItemType 事件。不同事件提供的参数不同,例如关系网格行事件与单元格事件并不共享完全相同的签名。
旧版 Classic Form 的 Form 事件中常见:
const item = document.thisItem;
const number = item.getProperty("item_number", "");
top.aras.AlertSuccess(`当前编号:${number}`);该示例只能证明 Classic Form 上下文中的思路。Responsive Form 并不保证提供相同 DOM 或 getFieldByName API;移植前应查目标 Release 的 Responsive Forms 文档。
Context Item 不是完整数据库快照
this 表示当前调用交给 Method 的 Item 节点。它包含哪些属性和关系,取决于请求。
string name = this.getProperty("name", "");
Item relationshipsInRequest = this.getRelationships("Requisition Detail");getRelationships() 只读取 context 当前已经携带的关系节点。更新请求没有提交关系时,结果为空并不代表数据库中没有关系。
需要数据库真值时显式查询
Innovator inn = this.getInnovator();
Item detailQuery = inn.newItem("Requisition Detail", "get");
detailQuery.setProperty("source_id", this.getID());
detailQuery.setAttribute("select", "id,quantity");
Item details = detailQuery.apply();
if (details.isError())
{
// 详细信息写入受控服务器日志,不回传给浏览器。
return inn.newError("明细查询失败,请联系管理员并提供操作时间。");
}
if (details.getItemCount() == 0)
{
return inn.newError("申请单至少需要一条明细。");
}
return this;这仍需结合事件类型判断:OnBeforeAdd 中源 Item 可能还没有持久化 ID,新关系可能只存在于请求 context。稳健校验通常需要同时理解请求内变化和数据库既有数据,而不是机械地只查一边。
一个可维护的服务端模板
Aras Server Method 编辑器会把代码嵌入平台生成的类中,通常不需要写完整 namespace 和 class。下面模板不默认提权,也不直接操作 SQL:
Innovator inn = this.getInnovator();
try
{
string itemNumber = this.getProperty("item_number", "").Trim();
if (itemNumber == "")
{
return inn.newError("item_number 不能为空。");
}
// 使用 IOM 完成查询或写入。
return this;
}
catch (Exception)
{
// 这里应通过项目的受控日志适配器记录详细异常;不要回传给浏览器。
return inn.newError("acme_ValidatePart 执行失败,请联系管理员并提供操作时间。");
}返回契约取决于调用方式和事件类型。上例适合需要返回 context 或 error 的校验场景,不代表所有 Server Event 都必须机械地 return this。
不要默认使用 Aras PLM 提权
用户资料里的旧模板会在每个 Method 开头授予高权限 Identity。这会改变正常访问控制,扩大任何代码缺陷的影响范围。只有当需求和威胁模型证明必须提权时,才授予最小 Identity,并用目标版本支持的 using / finally 模式确保释放。
错误处理与返回值
检查每一次 apply()
Item result = query.apply();
if (result.isError())
{
return inn.newError("查询失败,请联系管理员并提供操作时间。");
}
if (result.getItemCount() != 1)
{
return inn.newError("预期唯一结果,实际数量不符。");
}不要写 getItemCount() < 0 来判断“没有结果”。用户提供的工作流代码因此永远不会在正常的 0 条结果时进入创建分支。
不要手工回滚平台事务
CCO.DB.InnDatabase.RollbackTransaction() 不是普通业务校验 API。返回 inn.newError(...) 或抛出由平台处理的错误,让事件管线维护自己的事务边界。
外部副作用要设计幂等性
邮件、消息队列和外部 ERP 写入通常无法跟随 Innovator 数据库一起回滚。至少需要:
- 一个可重复计算的幂等键;
- 明确重试策略;
- 可查询的发送状态;
- 避免在可能回滚的事件里重复发送。
客户端代码的兼容性等级
| 等级 | 例子 | 文档策略 |
|---|---|---|
| 配置/公开能力 | Field/Form/Grid event,CUI Method | 主线讲解 |
| Classic 兼容 API | document.thisItem、部分 aras 方法 | 标明 Release 与界面类型 |
| 私有实现 | iframe 索引、grid_Experimental、内部 expression 方法 | 只作逆向案例,不承诺兼容 |
| 危险做法 | eval、无限轮询、把 UI 只读当授权 | 拒绝发布为正例 |
用户提供的页签、字段和搜索代码多数具有真实业务价值,但它们来自特定版本的 Classic Client。重构后的安全版本集中在 Cookbook 中,并标注兼容级别。
调试
客户端
在开发环境中使用浏览器开发者工具,并可临时加入:
debugger;
//# sourceURL=acme_client_method.js提交前移除 debugger。sourceURL 只帮助动态脚本在调试器中显示有意义的名称,不改变运行逻辑。
服务端
官方 Programmer's Guide 给出了启用 Server Method 调试、附加进程以及 System.Diagnostics.Debugger.Break() 的步骤。进程名称、配置键和运行宿主随版本变化。
只在隔离的 LDE 调试
Debugger.Launch() / Break() 会阻塞处理请求的服务器线程。不要在共享测试环境或生产环境触发交互式调试,也不要为了调试长期打开详细日志。
开发完成前的检查表
- Method 名包含公司/方案前缀,职责单一。
- 已记录调用位置、事件名、参数和返回值。
- 普通业务用户和无权限用户都经过验证。
apply()的错误和 0 / 1 / 多条结果分别处理。- 没有直接更新 Innovator 业务表。
- 没有默认高权限 Identity。
- 没有把 Classic 私有 DOM 当成 Responsive Form API。
- 外部副作用具备幂等和重试设计。
- 测试覆盖成功、校验失败、并发与重复调用。
