Open Source Web Application Framework for ASP.NET Core
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

9.3 KiB

//[doc-seo]
{
    "Description": "Add custom business logic to dynamic entity CRUD operations using Interceptors in the ABP Low-Code System. Validate, transform, and react to data changes with JavaScript."
}

Interceptors

Preview: Interceptors and their JavaScript context are preview extension points. Script context members, validation behavior, and lifecycle hooks may change before general availability.

Use designer actions and descriptor metadata for standard low-code behavior first. Interceptors are an advanced extension point for adding JavaScript lifecycle logic when the generated CRUD flow needs validation, transformation, or replacement behavior.

Interceptors allow you to run custom JavaScript code before, after, or instead of Create, Update, and Delete operations on dynamic entities.

Interceptor Types

Command Type When Executed
Create Pre Before entity creation — validation, default values
Create Post After entity creation — notifications, related data
Create Replace Instead of entity creation — must return the new entity's Id (see below)
Update Pre Before entity update — validation, authorization
Update Post After entity update — sync, notifications
Update Replace Instead of entity update — no return value needed
Delete Pre Before entity deletion — dependency checks
Delete Post After entity deletion — cleanup
Delete Replace Instead of entity deletion — no return value needed

Defining Interceptors in JSON Descriptors

The designer stores entity interceptors in the entity interceptors array:

{
  "name": "LowCodeDemo.Customers.Customer",
  "interceptors": [
    {
      "commandName": "Create",
      "type": "Pre",
      "javascript": "if (!args.getValue('Name')) {\n  globalError = 'Name is required.';\n}"
    }
  ]
}

Interceptor Descriptor

Field Type Description
commandName string "Create", "Update", or "Delete"
type string "Pre", "Post", or "Replace"
javascript string JavaScript code to execute

Defining Interceptors with Attributes

Use the [DynamicEntityCommandInterceptor] attribute on a C# class:

[DynamicEntity]
[DynamicEntityCommandInterceptor(
    "Create",
    InterceptorType.Pre,
    "if(!context.commandArgs.data['Name']) { globalError = 'Name is required!'; }"
)]
[DynamicEntityCommandInterceptor(
    "Create",
    InterceptorType.Post,
    "context.log('Entity created: ' + context.commandArgs.entityId);"
)]
public class Organization
{
    public string Name { get; set; }
}

The Name parameter must be one of: "Create", "Update", or "Delete". The InterceptorType can be Pre, Post, or Replace. When Replace is used, the default database operation is completely skipped and only your JavaScript handler executes. Multiple interceptors can be added to the same class (AllowMultiple = true).

Defining Interceptors with Fluent API

Use the Interceptors list on an EntityDescriptor to add interceptors programmatically in startup configuration:

AbpDynamicEntityConfig.EntityConfigurations.Configure(
    "MyApp.Organizations.Organization",
    entity =>
    {
        entity.Interceptors.Add(new CommandInterceptorDescriptor("Create")
        {
            Type = InterceptorType.Pre,
            Javascript = "if(!context.commandArgs.data['Name']) { globalError = 'Name is required!'; }"
        });

        entity.Interceptors.Add(new CommandInterceptorDescriptor("Delete")
        {
            Type = InterceptorType.Post,
            Javascript = "context.log('Deleted: ' + context.commandArgs.entityId);"
        });
    }
);

See Attributes & Fluent API for more details on Fluent API configuration.

JavaScript Context

Inside interceptor scripts, you have access to:

context.commandArgs

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 / 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
isInRole(roleName) function Check if user has a specific role

context.emailSender

Property / Method Description
isAvailable Whether the email sender is configured and available
sendAsync(to, subject, body) Send a plain-text email
sendAsync(from, to, subject, body) Send a plain-text email with an explicit sender
sendHtmlAsync(to, subject, htmlBody) Send an HTML email
sendHtmlAsync(from, to, subject, htmlBody) Send an HTML email with an explicit sender
queueAsync(to, subject, body) Queue a plain-text email
queueAsync(from, to, subject, body) Queue a plain-text email with an explicit sender
queueHtmlAsync(to, subject, htmlBody) Queue an HTML email
queueHtmlAsync(from, to, subject, htmlBody) Queue an HTML email with an explicit sender

Logging

Method Description
context.log(message) Log an informational message
context.logWarning(message) Log a warning message
context.logError(message) Log an error message

Use these methods instead of console.log (which is blocked in the sandbox).

db (Database API)

Full access to the Scripting API for querying and mutating data.

Interceptors can also use common scripting services such as user, tenant, auth, settings, features, config, http, events, jobs, blob, encryption, textTemplating, files, images, and attachments when they are enabled by the scripting capability profile.

globalError

Set this variable to a string to abort the operation and return an error:

globalError = 'Cannot delete this entity!';

Interceptor validation error displayed in the UI

Examples

Pre-Create: Validation

{
  "commandName": "Create",
  "type": "Pre",
  "javascript": "if(!context.commandArgs.data['Name']) {\n  globalError = 'Organization name is required!';\n}"
}

Post-Create: Email Notification

{
  "commandName": "Create",
  "type": "Post",
  "javascript": "if(context.currentUser.isAuthenticated && context.emailSender) {\n  await context.emailSender.sendAsync(\n    context.currentUser.email,\n    'New Order Created',\n    'Order total: $' + context.commandArgs.data['TotalAmount']\n  );\n}"
}

Pre-Update: Role-Based Authorization

{
  "commandName": "Update",
  "type": "Pre",
  "javascript": "if(context.commandArgs.data['IsDelivered']) {\n  if(!context.currentUser.roles.includes('admin')) {\n    globalError = 'Only administrators can mark orders as delivered!';\n  }\n}"
}

Pre-Delete: Business Rule Check

{
  "commandName": "Delete",
  "type": "Pre",
  "javascript": "var project = await db.get('LowCodeDemo.Projects.Project', context.commandArgs.entityId);\nif(project.Budget > 100000) {\n  globalError = 'Cannot delete high-budget projects!';\n}"
}

Pre-Update: Negative Value Check

{
  "commandName": "Update",
  "type": "Pre",
  "javascript": "if(context.commandArgs.data['Quantity'] < 0) {\n  globalError = 'Quantity cannot be negative!';\n}"
}

Replace-Create: Custom Insert Logic

When you need to completely replace the default create operation with custom logic:

{
  "commandName": "Create",
  "type": "Replace",
  "javascript": "var data = context.commandArgs.data;\ndata['Code'] = 'PRD-' + Date.now();\nvar result = await db.insert('LowCodeDemo.Products.Product', data);\ncontext.log('Product created with custom code: ' + data['Code']);\nreturn result.Id;"
}

Important: Replace-Create interceptors must return the new entity's Id (Guid). The system uses this value to fetch and return the created entity. Use return result.Id; after db.insert(...).

Replace-Update and Replace-Delete interceptors do not need to return a value.

Pre-Update: Self-Reference Check

{
  "commandName": "Update",
  "type": "Pre",
  "javascript": "if(context.commandArgs.data.ParentCategoryId === context.commandArgs.entityId) {\n  globalError = 'A category cannot be its own parent!';\n}"
}

See Also