diff --git a/docs/en/low-code/model-json.md b/docs/en/low-code/model-json.md
index ce59b0b3ec..2c11503553 100644
--- a/docs/en/low-code/model-json.md
+++ b/docs/en/low-code/model-json.md
@@ -362,9 +362,10 @@ Forms are named definitions referenced by pages through `formName`, `createFormN
"id": "details",
"title": "Details",
"isDefault": true,
- "rows": [
- { "cells": [{ "fieldId": "name", "colSpan": 4 }] },
- { "cells": [{ "fieldId": "status", "colSpan": 2 }, { "fieldId": "ownerId", "colSpan": 2 }] }
+ "fields": [
+ { "fieldId": "name", "row": 0, "colSpan": 4 },
+ { "fieldId": "status", "row": 1, "colSpan": 2 },
+ { "fieldId": "ownerId", "row": 1, "colSpan": 2 }
]
}
]
@@ -526,10 +527,12 @@ The complete example below shows the logical aggregate shape. Split projects sto
"id": "details",
"title": "Details",
"isDefault": true,
- "rows": [
- { "cells": [{ "fieldId": "name", "colSpan": 4 }] },
- { "cells": [{ "fieldId": "status", "colSpan": 2 }, { "fieldId": "budget", "colSpan": 2 }] },
- { "cells": [{ "fieldId": "startDate", "colSpan": 2 }, { "fieldId": "coverImage", "colSpan": 2 }] }
+ "fields": [
+ { "fieldId": "name", "row": 0, "colSpan": 4 },
+ { "fieldId": "status", "row": 1, "colSpan": 2 },
+ { "fieldId": "budget", "row": 1, "colSpan": 2 },
+ { "fieldId": "startDate", "row": 2, "colSpan": 2 },
+ { "fieldId": "coverImage", "row": 2, "colSpan": 2 }
]
}
]
diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbpDynamicSortingGuard.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbpDynamicSortingGuard.cs
index 25f7f72439..2f67547b0b 100644
--- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbpDynamicSortingGuard.cs
+++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbpDynamicSortingGuard.cs
@@ -16,9 +16,11 @@ namespace Volo.Abp.Application.Services;
/// constrained to plain property or field access. Methods, comparisons, ternaries
/// and constants in the sort key are rejected with .
/// Property-bag / shadow-property access through a constant string indexer
-/// (e.g. it["Prop"], Data["Prop"]) is treated as plain property access
-/// and allowed, since it carries no side effects and is the canonical way to sort
-/// dynamically-mapped entities.
+/// (e.g. it["Prop"], Data["Prop"]) is treated like plain property
+/// access and allowed, because it is the canonical way to sort dynamically-mapped
+/// entities. The guard assumes the matching indexer getter behaves like a property
+/// getter; defining an indexer that performs IO or mutates state will expose those
+/// effects through sorting.
///
internal static class AbpDynamicSortingGuard
{
@@ -88,9 +90,10 @@ internal static class AbpDynamicSortingGuard
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// Allow property-bag / shadow-property access through a constant string
- // indexer (it["Prop"], Data["Prop"], mainEntity["Prop"], ...). This is a
- // pure read of a named member, not an arbitrary method invocation, so it
- // does not open the injection surface the guard protects against.
+ // indexer (it["Prop"], Data["Prop"], mainEntity["Prop"], ...). The
+ // constant key cannot smuggle in an arbitrary method invocation, so this
+ // does not widen the injection surface the guard protects against; it
+ // treats the indexer access like ordinary property access.
if (IsConstantStringIndexer(node))
{
if (node.Object != null)
diff --git a/schemas/low-code/definitions/form-descriptor.schema.json b/schemas/low-code/definitions/form-descriptor.schema.json
index 3508ff58a9..102193ee10 100644
--- a/schemas/low-code/definitions/form-descriptor.schema.json
+++ b/schemas/low-code/definitions/form-descriptor.schema.json
@@ -34,7 +34,7 @@
},
"layout": {
"$ref": "form-layout-descriptor.schema.json",
- "description": "Visual layout for the fields. Every layout cell fieldId must refer to a field in fields."
+ "description": "Visual layout for the fields. Every layout placement fieldId must refer to a field in fields."
},
"rules": {
"type": "array",