Skip to content

Method 开发与事件上下文

Method 是 Aras Innovator 中承载自定义业务逻辑的 Item。它可以作为 Item Action、通用方法、服务器事件或客户端事件运行。真正决定代码含义的,不只是语言,而是调用位置、上下文 Item、事务阶段和调用者权限

适用范围

  • Method 与 IOM 的基本模型跨版本较稳定。
  • 本文的服务器事件语义以 Aras Innovator 35 Programmer's Guide 为主要基线。
  • 用户资料中的 document.thisItemrelTabbar、iframe、grid.items_Experimental 等属于 Classic Client 或私有实现;不能当成跨版本公共 API。

先判断应该写在哪一层

需求首选位置原因
显示/隐藏字段、友好提示客户端或响应式表单配置直接改善交互
防止非法数据保存服务端事件不能被其他客户端或 API 绕过
数据查询与业务计算服务端 Method权限、事务与错误返回一致
外部系统集成IOM / REST 调用与网页生命周期解耦
页面只读UI 配置 + 服务端权限UI 只读本身不是访问控制

一条实用原则

客户端校验负责“尽早告诉用户”,服务端校验负责“最终保证数据”。任何会影响数据正确性或授权的规则,都不能只写在 JavaScript 中。

Method 的几种调用模型

先用调用位置判断 this,不要把所有 JavaScript Method 都写成同一种上下文:

Classic Client 调用位置this 的常见含义当前 Item 的常见入口
Item Action被操作的 Itemthis
Form / Grid 事件浏览器 documentdocument.thisItem 或该事件公开参数
Field 事件字段控件从字段/表单上下文取得
Relationship Grid关系网格上下文常见旧实现为 parent.thisItem,需按版本核验

这是 R29 Programmer's Guide 对 Classic Client 的上下文模型。Responsive Form 或新客户端扩展点不能直接套用这张表。

Item Action

Item Action 把 Method 暴露为某个 Item 的动作。上下文通常是被操作的 Item,但仍应校验类型:

csharp
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 中,方法必须把它们当作不可信输入:

csharp
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();

调用端:

csharp
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 会回滚。因此不要把 OnAfterAddOnAfterUpdate 写成“数据库已经提交”的钩子,也不要在这里盲目调用不可回滚的外部副作用。

客户端事件

客户端 Method 使用 JavaScript,可绑定 Form、Field、Grid 或 ItemType 事件。不同事件提供的参数不同,例如关系网格行事件与单元格事件并不共享完全相同的签名。

旧版 Classic Form 的 Form 事件中常见:

js
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 节点。它包含哪些属性和关系,取决于请求。

csharp
string name = this.getProperty("name", "");
Item relationshipsInRequest = this.getRelationships("Requisition Detail");

getRelationships() 只读取 context 当前已经携带的关系节点。更新请求没有提交关系时,结果为空并不代表数据库中没有关系。

需要数据库真值时显式查询

csharp
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:

csharp
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()

csharp
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 数据库一起回滚。至少需要:

  1. 一个可重复计算的幂等键;
  2. 明确重试策略;
  3. 可查询的发送状态;
  4. 避免在可能回滚的事件里重复发送。

客户端代码的兼容性等级

等级例子文档策略
配置/公开能力Field/Form/Grid event,CUI Method主线讲解
Classic 兼容 APIdocument.thisItem、部分 aras 方法标明 Release 与界面类型
私有实现iframe 索引、grid_Experimental、内部 expression 方法只作逆向案例,不承诺兼容
危险做法eval、无限轮询、把 UI 只读当授权拒绝发布为正例

用户提供的页签、字段和搜索代码多数具有真实业务价值,但它们来自特定版本的 Classic Client。重构后的安全版本集中在 Cookbook 中,并标注兼容级别。

调试

客户端

在开发环境中使用浏览器开发者工具,并可临时加入:

js
debugger;
//# sourceURL=acme_client_method.js

提交前移除 debuggersourceURL 只帮助动态脚本在调试器中显示有意义的名称,不改变运行逻辑。

服务端

官方 Programmer's Guide 给出了启用 Server Method 调试、附加进程以及 System.Diagnostics.Debugger.Break() 的步骤。进程名称、配置键和运行宿主随版本变化。

只在隔离的 LDE 调试

Debugger.Launch() / Break() 会阻塞处理请求的服务器线程。不要在共享测试环境或生产环境触发交互式调试,也不要为了调试长期打开详细日志。

开发完成前的检查表

  • Method 名包含公司/方案前缀,职责单一。
  • 已记录调用位置、事件名、参数和返回值。
  • 普通业务用户和无权限用户都经过验证。
  • apply() 的错误和 0 / 1 / 多条结果分别处理。
  • 没有直接更新 Innovator 业务表。
  • 没有默认高权限 Identity。
  • 没有把 Classic 私有 DOM 当成 Responsive Form API。
  • 外部副作用具备幂等和重试设计。
  • 测试覆盖成功、校验失败、并发与重复调用。

相关主题

官方资料

本站内容仅供学习与参考