Browse Source

docs(low-code): expand APIs, endpoints, and exports

Expand Low-Code documentation:

- Custom Endpoints: added request/headers/params variables, many response helpers (created, noContent, unauthorized, forbidden, error, response) and logging helpers.
- Fluent API: added Enum localization section, switched to property-based API names (DefaultDisplayPropertyName, AddOrGetProperty, etc.), added examples for chaining, foreign keys and interceptors, and generic Configure<T> usage.
- Index: documented Export (Excel/CSV) flow and endpoints, plus Custom Commands/Queries usage and discovery; clarified DynamicEntityAppService and UI app service methods.
- Interceptors & Scripting API: expanded context.commandArgs, context.currentUser and context.emailSender surfaces; added logging methods; added db.exists and updated available scripting methods.
- model.json: added validator examples and a comprehensive validator table (url, phone, regularExpression, range, min/max, etc.).
- reference-entities: removed a couple of reference rows (IdentityRole, Tenant).

These edits improve developer guidance and add examples for using the low-code features.
pull/25021/head
SALİH ÖZKARA 1 month ago
parent
commit
a3a33e4ae8
  1. 22
      docs/en/low-code/custom-endpoints.md
  2. 127
      docs/en/low-code/fluent-api.md
  3. 76
      docs/en/low-code/index.md
  4. 33
      docs/en/low-code/interceptors.md
  5. 53
      docs/en/low-code/model-json.md
  6. 2
      docs/en/low-code/reference-entities.md
  7. 15
      docs/en/low-code/scripting-api.md

22
docs/en/low-code/custom-endpoints.md

@ -61,18 +61,36 @@ Inside custom endpoint scripts, you have access to:
| Variable | Description |
|----------|-------------|
| `request` | Full request object |
| `route` | Route parameter values (e.g., `route.id`) |
| `params` | Alias for route parameters |
| `query` | Query string parameters (e.g., `query.q`, `query.page`) |
| `body` | Request body (for POST/PUT) |
| `user` | Current user (same as `context.currentUser` in interceptors) |
| `headers` | Request headers |
| `user` | Current user (same as `context.currentUser` in [Interceptors](interceptors.md)) |
| `email` | Email sender (same as `context.emailSender` in [Interceptors](interceptors.md)) |
### Response Helpers
| Function | HTTP Status | Description |
|----------|-------------|-------------|
| `ok(data)` | 200 | Success response with data |
| `notFound(message)` | 404 | Not found response |
| `created(data)` | 201 | Created response with data |
| `noContent()` | 204 | No content response |
| `badRequest(message)` | 400 | Bad request response |
| `unauthorized(message)` | 401 | Unauthorized response |
| `forbidden(message)` | 403 | Forbidden response |
| `notFound(message)` | 404 | Not found response |
| `error(message)` | 500 | Internal server error response |
| `response(statusCode, data, error)` | Custom | Custom status code response |
### Logging
| Function | Description |
|----------|-------------|
| `log(message)` | Log an informational message |
| `logWarning(message)` | Log a warning message |
| `logError(message)` | Log an error message |
### Database API

127
docs/en/low-code/fluent-api.md

@ -248,6 +248,24 @@ public class Organization
}
````
### Enum Localization
Enum values can be localized using ABP's localization system. Add localization keys in the format `Enum:{EnumTypeName}.{ValueName}` to your localization JSON files:
```json
{
"culture": "en",
"texts": {
"Enum:OrganizationType.Corporate": "Corporate",
"Enum:OrganizationType.Enterprise": "Enterprise",
"Enum:OrganizationType.Startup": "Startup",
"Enum:OrganizationType.Consulting": "Consulting"
}
}
```
The Blazor UI automatically uses these localization keys for enum dropdowns and display values. If no localization key is found, the enum member name is used as-is.
## Fluent API
The Fluent API has the **highest priority** in the configuration system. Use `AbpDynamicEntityConfig.EntityConfigurations` to override any attribute or JSON setting programmatically.
@ -263,47 +281,98 @@ public override void ConfigureServices(ServiceConfigurationContext context)
"MyApp.Products.Product",
entity =>
{
entity.SetDisplayProperty("Name");
entity.DefaultDisplayPropertyName = "Name";
entity.ConfigureProperty("Price", prop =>
{
prop.SetRequired(true);
prop.SetUI(ui =>
{
ui.SetDisplayName("Unit Price");
ui.SetCreationFormAvailability(EntityPropertyUIFormAvailability.Available);
});
});
entity.ConfigureProperty("InternalNotes", prop =>
var priceProperty = entity.AddOrGetProperty("Price");
priceProperty.AsRequired();
priceProperty.UI = new EntityPropertyUIDescriptor
{
prop.SetServerOnly(true);
});
DisplayName = "Unit Price",
CreationFormAvailability = EntityPropertyUIFormAvailability.Available
};
entity.AddOrGetProperty("InternalNotes").AsServerOnly();
}
);
}
````
### Entity Configuration Methods
You can also use the generic overload with a type parameter:
````csharp
AbpDynamicEntityConfig.EntityConfigurations.Configure<Product>(entity =>
{
entity.DefaultDisplayPropertyName = "Name";
});
````
### Entity Configuration
| Method | Description |
The `Configure` method provides an `EntityDescriptor` instance. You can set its properties directly:
| Property / Method | Description |
|--------|-------------|
| `SetDisplayProperty(name)` | Set the display property for lookups |
| `SetParent(entityName)` | Set parent entity for nesting |
| `SetUI(action)` | Configure entity-level UI |
| `ConfigureProperty(name, action)` | Configure a specific property |
| `AddInterceptor(name, type, js)` | Add a JavaScript interceptor. `name`: `"Create"`, `"Update"`, or `"Delete"`. `type`: `Pre`, `Post`, or `Replace`. `Replace-Create` must return the new entity's Id |
| `DefaultDisplayPropertyName` | Set the display property for lookups |
| `Parent` | Set parent entity name for nesting |
| `UI` | Set entity-level UI (`EntityUIDescriptor` with `PageTitle`) |
| `AddOrGetProperty(name)` | Get or create a property descriptor for configuration |
| `FindProperty(name)` | Find a property descriptor by name (returns `null` if not found) |
| `GetProperty(name)` | Get a property descriptor by name (throws if not found) |
| `Interceptors` | List of `CommandInterceptorDescriptor` — add interceptors directly |
### Property Configuration
### Property Configuration Methods
`AddOrGetProperty` returns an `EntityPropertyDescriptor`. Configure it using direct property assignment and extension methods:
| Method | Description |
| Property / Extension Method | Description |
|--------|-------------|
| `SetRequired(bool)` | Mark as required |
| `SetUnique(bool)` | Mark as unique |
| `SetServerOnly(bool)` | Hide from clients |
| `SetAllowSetByClients(bool)` | Allow client writes |
| `SetForeignKey(entityName, displayProp, access)` | Configure foreign key |
| `SetUI(action)` | Configure property UI |
| `.AsRequired(bool)` | Mark as required (extension method, returns the descriptor for chaining) |
| `.AsServerOnly(bool)` | Hide from clients (extension method, returns the descriptor for chaining) |
| `.MapToDbField(bool)` | Control if property is stored in DB (extension method, returns the descriptor for chaining) |
| `.IsUnique` | Set to `true` to mark as unique |
| `.AllowSetByClients` | Set to `false` to prevent client writes |
| `.ForeignKey` | Set a `ForeignKeyDescriptor` to configure foreign key relationship |
| `.UI` | Set an `EntityPropertyUIDescriptor` to configure property UI |
### Chaining Extension Methods
The extension methods `AsRequired()`, `AsServerOnly()`, and `MapToDbField()` return the property descriptor, enabling fluent chaining:
````csharp
entity.AddOrGetProperty("InternalNotes")
.AsServerOnly()
.AsRequired()
.MapToDbField();
````
### Configuring Foreign Keys
````csharp
AbpDynamicEntityConfig.EntityConfigurations.Configure(
"MyApp.Orders.Order",
entity =>
{
var customerIdProperty = entity.AddOrGetProperty("CustomerId");
customerIdProperty.ForeignKey = new ForeignKeyDescriptor
{
EntityName = "MyApp.Customers.Customer",
DisplayPropertyName = "Name",
Access = ForeignAccess.Edit
};
}
);
````
### Adding Interceptors
````csharp
entity.Interceptors.Add(new CommandInterceptorDescriptor
{
CommandName = "Create",
Type = InterceptorType.Pre,
Javascript = "if(!context.commandArgs.data['Name']) { globalError = 'Name is required!'; }"
});
````
## Assembly Registration

76
docs/en/low-code/index.md

@ -170,6 +170,74 @@ See [model.json Structure](model-json.md) for the full specification.
| **Scripting API** | Server-side JavaScript for database queries and CRUD | [Scripting API](scripting-api.md) |
| **Custom Endpoints** | REST APIs with JavaScript handlers | [Custom Endpoints](custom-endpoints.md) |
| **Foreign Access** | View/Edit related entities from the parent's UI | [Foreign Access](foreign-access.md) |
| **Export** | Export entity data to Excel (XLSX) or CSV | See below |
## Export (Excel / CSV)
The Low-Code System provides built-in export functionality for all dynamic entities. Users can export filtered data to **Excel (XLSX)** or **CSV** directly from the Blazor UI.
### How It Works
1. The client calls `GET /api/low-code/entities/{entityName}/download-token` to obtain a single-use download token (valid for 30 seconds).
2. The client calls `GET /api/low-code/entities/{entityName}/export-as-excel` or `GET /api/low-code/entities/{entityName}/export-as-csv` with the token and optional filters.
### API Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /api/low-code/entities/{entityName}/download-token` | Get a single-use download token |
| `GET /api/low-code/entities/{entityName}/export-as-excel` | Export as Excel (.xlsx) |
| `GET /api/low-code/entities/{entityName}/export-as-csv` | Export as CSV (.csv) |
Export requests accept the same filtering, sorting, and search parameters as the list endpoint. Server-only properties are automatically excluded, and foreign key columns display the referenced entity's display value instead of the raw ID.
## Custom Commands and Queries
The Low-Code System allows you to replace or extend the default CRUD operations by implementing custom command and query handlers in C#.
### Custom Commands
Create a class that implements `ILcCommand<TResult>` and decorate it with `[CustomCommand]`:
````csharp
[CustomCommand("Create", "MyApp.Products.Product")]
public class CustomProductCreateCommand : LcCommandBase<DynamicEntityDto>
{
public override async Task<DynamicEntityDto> ExecuteWithResultAsync(DynamicCommandArgs commandArgs)
{
// Your custom create logic here
// ...
}
}
````
| Parameter | Description |
|-----------|-------------|
| `commandName` | The command to replace: `"Create"`, `"Update"`, or `"Delete"` |
| `entityName` | Full entity name (e.g., `"MyApp.Products.Product"`) |
### Custom Queries
Create a class that implements `ILcQuery<TResult>` and decorate it with `[CustomQuery]`:
````csharp
[CustomQuery("List", "MyApp.Products.Product")]
public class CustomProductListQuery : ILcQuery<DynamicQueryResult>
{
public async Task<DynamicQueryResult> ExecuteAsync(DynamicQueryArgs queryArgs)
{
// Your custom list query logic here
// ...
}
}
````
| Parameter | Description |
|-----------|-------------|
| `queryName` | The query to replace: `"List"` or `"Single"` |
| `entityName` | Full entity name (e.g., `"MyApp.Products.Product"`) |
Custom commands and queries are automatically discovered and registered at startup. They completely replace the default handler for the specified entity and operation.
## Internals
@ -182,8 +250,12 @@ See [model.json Structure](model-json.md) for the full specification.
### Application Layer
* `DynamicEntityAppService`: CRUD operations for all dynamic entities.
* `DynamicEntityUIAppService`: UI definitions, menu items, and page configurations.
* `DynamicEntityAppService`: CRUD operations for all dynamic entities (Get, GetList, Create, Update, Delete, Export).
* `DynamicEntityUIAppService`: UI definitions, menu items, and page configurations. Provides:
* `GetUiDefinitionAsync(entityName)` — Full UI definition (filters, columns, forms, children, foreign access actions, permissions)
* `GetUiCreationFormDefinitionAsync(entityName)` — Creation form fields with validation rules
* `GetUiEditFormDefinitionAsync(entityName)` — Edit form fields with validation rules
* `GetMenuItemsAsync()` — Menu items for all entities that have a `pageTitle` configured (filtered by permissions)
* `DynamicPermissionDefinitionProvider`: Auto-generates permissions per entity.
* `CustomEndpointExecutor`: Executes JavaScript-based custom endpoints.

33
docs/en/low-code/interceptors.md

@ -78,32 +78,51 @@ Inside interceptor scripts, you have access to:
### `context.commandArgs`
| Property | Type | Description |
| Property / Method | Type | Description |
|----------|------|-------------|
| `data` | object | Entity data dictionary (for Create/Update) |
| `entityId` | string | Entity ID (for Update/Delete) |
| `commandName` | string | Command name (`"Create"`, `"Update"`, or `"Delete"`) |
| `entityName` | string | Full entity name |
| `getValue(name)` | function | Get a property value |
| `setValue(name, value)` | function | Set a property value (Pre-interceptors only) |
| `hasValue(name)` | function | Check if a property exists in the data |
| `removeValue(name)` | function | Remove a property from the data |
### `context.currentUser`
| Property | Type | Description |
| Property / Method | Type | Description |
|----------|------|-------------|
| `isAuthenticated` | bool | Whether user is logged in |
| `id` | string | User ID |
| `userName` | string | Username |
| `email` | string | Email address |
| `name` | string | First name |
| `surName` | string | Last name |
| `phoneNumber` | string | Phone number |
| `phoneNumberVerified` | bool | Whether phone is verified |
| `emailVerified` | bool | Whether email is verified |
| `tenantId` | string | Tenant ID (for multi-tenant apps) |
| `roles` | string[] | User's role names |
| `id` | string | User ID |
| `isInRole(roleName)` | function | Check if user has a specific role |
### `context.emailSender`
| Method | Description |
| Property / Method | Description |
|--------|-------------|
| `sendAsync(to, subject, body)` | Send an email |
| `isAvailable` | Whether the email sender is configured and available |
| `sendAsync(to, subject, body)` | Send a plain-text email |
| `sendHtmlAsync(to, subject, htmlBody)` | Send an HTML email |
### `context.log(message)`
### Logging
| Method | Description |
|--------|-------------|
| `context.log(message)` | Log an informational message |
| `context.logWarning(message)` | Log a warning message |
| `context.logError(message)` | Log an error message |
Log a message (use instead of `console.log`).
> Use these methods instead of `console.log` (which is blocked in the sandbox).
### `db` (Database API)

53
docs/en/low-code/model-json.md

@ -206,13 +206,52 @@ Add validation rules to properties:
}
```
| Validator | Parameters | Description |
|-----------|------------|-------------|
| `required` | — | Value is required |
| `minLength` | `length` | Minimum string length |
| `maxLength` | `length` | Maximum string length |
| `emailAddress` | — | Must be a valid email |
| `range` | `min`, `max` | Numeric range |
Additional validator examples:
```json
{
"name": "Website",
"validators": [
{ "type": "url", "message": "Please enter a valid URL" }
]
},
{
"name": "PhoneNumber",
"validators": [
{ "type": "phone" }
]
},
{
"name": "ProductCode",
"validators": [
{ "type": "regularExpression", "pattern": "^[A-Z]{3}-\\d{4}$", "message": "Code must be in format ABC-1234" }
]
},
{
"name": "Price",
"type": "decimal",
"validators": [
{ "type": "range", "minimum": 0.01, "maximum": 99999.99 }
]
}
```
| Validator | Parameters | Applies To | Description |
|-----------|------------|------------|-------------|
| `required` | `allowEmptyStrings` (optional) | All types | Value is required |
| `minLength` | `length` | String | Minimum string length |
| `maxLength` | `length` | String | Maximum string length |
| `stringLength` | `minimumLength`, `maximumLength` | String | String length range (min and max together) |
| `emailAddress` | — | String | Must be a valid email |
| `phone` | — | String | Must be a valid phone number |
| `url` | — | String | Must be a valid URL |
| `creditCard` | — | String | Must be a valid credit card number |
| `regularExpression` | `pattern` | String | Must match the regex pattern |
| `range` | `minimum`, `maximum` | Numeric | Numeric range |
| `min` | `minimum` | Numeric | Minimum numeric value |
| `max` | `maximum` | Numeric | Maximum numeric value |
> All validators accept an optional `message` parameter for a custom error message. The `regularExpression` validator also accepts the alias `pattern`, and `emailAddress` also accepts `email`.
## UI Configuration

2
docs/en/low-code/reference-entities.md

@ -118,8 +118,6 @@ if (user) {
| Entity | Name for `entityName` | Typical Display Property |
|--------|----------------------|--------------------------|
| ABP Identity User | `Volo.Abp.Identity.IdentityUser` | `UserName` |
| ABP Identity Role | `Volo.Abp.Identity.IdentityRole` | `Name` |
| ABP Tenant | `Volo.Saas.Tenant` | `Name` |
## See Also

15
docs/en/low-code/scripting-api.md

@ -57,7 +57,6 @@ var result = await db.query('LowCodeDemo.Products.Product')
| `thenByDescending(x => x.Property)` | Secondary sort descending | `QueryBuilder` |
| `skip(n)` | Skip n records | `QueryBuilder` |
| `take(n)` | Take n records | `QueryBuilder` |
| `reverse()` | Reverse sort order | `QueryBuilder` |
| `toList()` | Execute and return array | `Promise<object[]>` |
| `count()` | Return count | `Promise<number>` |
| `any()` | Check if any matches exist | `Promise<boolean>` |
@ -71,7 +70,6 @@ var result = await db.query('LowCodeDemo.Products.Product')
| `select(x => projection)` | Project to custom shape | `QueryBuilder` |
| `join(entity, alias, condition)` | Inner join | `QueryBuilder` |
| `leftJoin(entity, alias, condition)` | Left join | `QueryBuilder` |
| `chunk(size)` | Split into chunks | `Promise<object[][]>` |
### Supported Operators in Lambda
@ -279,6 +277,7 @@ Direct CRUD methods on the `db` object:
|--------|-------------|---------|
| `db.get(entityName, id)` | Get by ID | `Promise<object\|null>` |
| `db.getCount(entityName)` | Get count | `Promise<number>` |
| `db.exists(entityName)` | Check if any records exist | `Promise<boolean>` |
| `db.insert(entityName, entity)` | Insert new | `Promise<object>` |
| `db.update(entityName, entity)` | Update existing | `Promise<object>` |
| `db.delete(entityName, id)` | Delete by ID | `Promise<void>` |
@ -311,12 +310,16 @@ Available in [interceptors](interceptors.md):
| Property | Type | Description |
|----------|------|-------------|
| `context.commandArgs` | object | Command arguments (data, entityId) |
| `context.commandArgs` | object | Command arguments (data, entityId, commandName, entityName) |
| `context.commandArgs.getValue(name)` | function | Get property value |
| `context.commandArgs.setValue(name, value)` | function | Set property value |
| `context.currentUser` | object | Current user (isAuthenticated, userName, email, roles, id) |
| `context.emailSender` | object | Email sending (`sendAsync(to, subject, body)`) |
| `context.log(msg)` | function | Logging |
| `context.commandArgs.hasValue(name)` | function | Check if a property exists |
| `context.commandArgs.removeValue(name)` | function | Remove a property value |
| `context.currentUser` | object | Current user info (see [Interceptors](interceptors.md) for full list) |
| `context.emailSender` | object | Email sending (`sendAsync`, `sendHtmlAsync`) |
| `context.log(msg)` | function | Log an informational message |
| `context.logWarning(msg)` | function | Log a warning message |
| `context.logError(msg)` | function | Log an error message |
## Security

Loading…
Cancel
Save