BusinessActivity
Category: Common Steps
Namespace: CMS.Plugin.FlowManagement.Abstractions.FlowBusiness.Activitys
Base Class: Activity (SYC.Flow.Kernel)
Module: FlowManagement.Abstractions
Overview
BusinessActivity is the base class for all business flow nodes in the LMES flow system. It inherits from SYC.Flow.Kernel.Activity and provides basic functionality and lifecycle management for flow nodes, including complete flow control for node entry, execution, and exit, as well as flow context data management, exception handling, and logging.
All custom flow nodes should inherit from BusinessActivity or its subclasses (such as ActionActivity, ProcessBusinessActivity) to obtain complete flow management capabilities.
Business Scenarios
Applicable Scenarios
- Use as Base Class: Base class for all custom flow nodes
- Flow Control: Provides node lifecycle management (Enter, Execute, Exit)
- Data Management: Manages read/write of flow context data
- Exception Handling: Unified exception handling and error recovery mechanism
- Logging: Standardized logging functionality
Role in System
BusinessActivity is the core foundation class of the entire flow system. It:
- Defines the standard lifecycle of flow nodes
- Provides access interface for flow context (Flow.DataItems)
- Implements unified exception handling mechanism
- Supports publishing and subscribing to flow events
- Provides extended property mechanism supporting dynamic configuration
Collaboration with Other Nodes
- ActionActivity: Inherits from BusinessActivity, adds execution timing control
- ProcessBusinessActivity: Inherits from BusinessActivity, specifically for MES process business
- All Business Nodes: Directly or indirectly inherit from BusinessActivity
Configuration Description
Basic Configuration
| Property Name | Type | Required | Default | Description |
|---|---|---|---|---|
| TaskDelay | int | No | 0 | Execute delay time (Unit: ms) |
| IsDisabled | bool | No | false | Mask business code ProcessAsync() in the step, will skip business and continue processing |
| PublishFlowEvent | bool | No | false | Whether to publish flow events |
| PublishDeliveryEvent | bool | No | false | Whether to publish delivery events |
| IsPrintConsumption | bool | No | true | Whether to print execution time consumption |
| StopwatchType | StopwatchType | No | Common | Timing type (Common/Start/Stop/Reset/Restart) |
| ExtendedProperty | BusinessPropertyCollection | No | Empty Collection | Step extended properties |
| FlowExceptionMaxCount | int | No | 10 | Flow exception limit count |
| SaveFlowInstance | bool | No | false | Save instance to database |
Configuration Details
TaskDelay
Description: Sets the delay time before node execution, in milliseconds. Waits for the specified time at the beginning of the ExecuteAsync method.
Value Range: Integer greater than or equal to 0
Notes:
- Responds to flow cancellation requests during delay
- Excessive delay may affect flow response performance
- Usually used for debugging or special timing control scenarios
IsDisabled
Description: When set to true, the node's ProcessAsync method will be skipped, but the node's lifecycle methods (EnterAsync, ExitAsync) will still execute.
Value Range: true or false
Notes:
- Can be used to temporarily disable the business logic of a node
- Flow context data will still be passed normally
- Can be dynamically controlled via flow context:
{NodeName}_IsActivityDisabled(NodeName is the value of the node's Name property)
PublishFlowEvent
Description: Whether to publish a flow event (ProcessFlowEto) after node execution, which other modules can subscribe to.
Value Range: true or false
Notes:
- The event contains all data of the current flow context
- Synchronous or asynchronous publishing can be controlled via EventSynchronous property
- Frequent event publishing may affect performance
ExtendedProperty
Description: Extended property collection, allowing custom properties to be added to the node, which can be written to the flow context.
Value Range: BusinessPropertyCollection object
Notes:
- Each extended property can be configured to write to flow context (WriteIntoDataItems)
- Values of extended properties will automatically have "_Value" suffix added as flow context keys
- Supports dynamic configuration at runtime
FlowExceptionMaxCount
Description: Flow exception count limit. When the flow exception count exceeds this value, the node will refuse entry.
Value Range: Integer greater than or equal to -1, -1 means no limit
Notes:
- Used to prevent flow from entering infinite exception loops
- Exception counting is statistical based on flow name (ProcName)
- Requires restarting flow or clearing exception count after reaching the limit
Flow Context
Input Parameters
| Parameter Name | Type | Description |
|---|---|---|
| {NodeName}_IsActivityDisabled | bool | Dynamically control whether node is disabled |
| ApplicationData | object | Application data (such as ProcessModel) |
| Custom Parameters | any | Business data written by upstream nodes |
Output Parameters
| Parameter Name | Type | Description |
|---|---|---|
| Extended Property Value | any | Property values configured in ExtendedProperty |
| Custom Parameters | any | Business data written in ProcessAsync |
Data Flow Description
- Read Data: Read flow context data via
Flow.DataItems[key] - Write Data: Write flow context data via
Flow.DataItems[key] = value - Helper Methods:
GetFlowItemValue(key): Supports dot-separated property paths and array indicesGetVariableValue(key): Supports variable reference in{key}formatToFlowItemKey(key): Automatically adds "_Value" suffix
Business Logic Description
Processing Flow
The complete lifecycle of BusinessActivity includes the following steps:
-
CanEnterAsync: Determine if node can be entered
- Check if flow exception count exceeds limit
- Check if flow is cancelled
- Check if flow status is normal
-
EnterAsync: Enter node
- Create work item (FlowItem)
- Record start time
- Update current task information
- Record entry log
-
ExecuteAsync: Execute node
- Execute delay (if TaskDelay is configured)
- Call ProcessAsync to execute business logic
- Publish flow event (if enabled)
- Process extended properties
-
ProcessAsync: Business logic processing
- Default implementation is empty, overridden by subclasses
-
CanExitAsync: Determine if node can be exited
- Default returns true
-
ExitAsync: Exit node
- Record end time
- Save flow instance (if enabled)
- Print execution time consumption
- Handle timer operations
Flow Chart
[CanEnterAsync]
↓ (true)
[EnterAsync]
↓
[ExecuteAsync]
↓
[ProcessAsync] (Business Logic)
↓
[CanExitAsync]
↓ (true)
[ExitAsync]
↓
[Next Node]
Dependency Services
| Service Interface | Purpose | Description |
|---|---|---|
| ILmesMetrics | Performance metrics collection | Optional, used to collect flow execution metrics |
| ILocalEventBus | Event bus | Used to publish flow events and delivery events |
| Flow.ServiceProvider | Service provider | Access services in dependency injection container |
Exception Handling
| Exception Type | Handling Method | Description |
|---|---|---|
| BusinessException | Mark flow status as Unexpected | Business exception in ProcessAsync |
| TaskCanceledException | Record error log | Flow cancelled |
| OperationCanceledException | Record error log | Operation cancelled |
| Other Exceptions | Record exception log and mark flow status | Unexpected exceptions |
Exception Handling Mechanism:
- Exceptions in ProcessAsync are captured and wrapped as BusinessException
- Exceptions increase flow exception count
- Node refuses entry after reaching exception limit
- Exceptions trigger flow reset (ResetEventAsync)
Logging
BusinessActivity records the following key logs:
- Enter Node:
Enter, TaskID={taskId} - Execute Delay: Exceptions during delay
- Business Exception: Exception details in ProcessAsync
- Flow Event: Start and end of publishing flow events
- Exit Node:
Exit, Time Consumption={milliseconds}ms - Timeout Warning: Recorded when execution time exceeds TimeOut
Usage Examples
Basic Example
// Simplest custom node
[Serializable]
[Design("Simple Node", "Simple business node example", Sort = 1)]
[Category("Custom")]
public class SimpleActivity : BusinessActivity
{
public override async Task ProcessAsync(ProcessflowEventArgs args)
{
// Read input data
var input = Flow.DataItems["InputData"]?.ToString();
// Process business logic
var result = $"Processed: {input}";
// Write output data
Flow.DataItems["OutputData"] = result;
// Record log
Flow.Logger.LogMessage($"Processing complete: {result}", Name);
await base.ProcessAsync(args);
}
}
Advanced Example
// Node with configuration properties and exception handling
[Serializable]
[Design("Advanced Node", "Business node example with configuration", Sort = 2)]
[Category("Custom")]
public class AdvancedActivity : BusinessActivity
{
[Design("Config Item 1", "First configuration item", Sort = 1)]
[Category("Configuration")]
[DataMember]
public string Config1 { get; set; }
[Design("Config Item 2", "Second configuration item", Sort = 2)]
[Category("Configuration")]
[DataMember]
[Required]
public int Config2 { get; set; }
public override List<FlowItemKey> FlowItemKeys => new()
{
new FlowItemKey("Input_Value", "Input Parameter", "Input parameter description", typeof(string)),
new FlowItemKey("Output_Value", "Output Parameter", "Output parameter description", typeof(string))
};
public override async Task ProcessAsync(ProcessflowEventArgs args)
{
try
{
// Read input
var input = GetFlowItemValue("Input")?.ToString();
if (string.IsNullOrEmpty(input))
{
Flow.Logger.LogWarningMessage("Input parameter is empty", Name);
return;
}
// Use configuration items
Flow.Logger.LogMessage($"Config1={Config1}, Config2={Config2}", Name);
// Call external service
var service = Flow.ServiceProvider.GetRequiredService<IMyService>();
var result = await service.ProcessAsync(input, Config2);
// Write output
Flow.DataItems["Output_Value"] = result;
Flow.Logger.LogMessage($"Processing success: {result}", Name);
}
catch (Exception ex)
{
Flow.Logger.LogExceptionMessage(ex, Name);
throw;
}
await base.ProcessAsync(args);
}
}
Complete Flow Example
{
"Name": "Example Flow",
"Activities": [
{
"Type": "SimpleActivity",
"Name": "Start Node",
"Alias": "Start",
"ExtendedProperty": [
{
"Name": "InputData",
"Value": "Initial Data",
"WriteIntoDataItems": true
}
]
},
{
"Type": "AdvancedActivity",
"Name": "Process Node",
"Alias": "Process",
"Config1": "ConfigValue1",
"Config2": 100,
"TaskDelay": 1000,
"IsPrintConsumption": true
},
{
"Type": "EndActivity",
"Name": "End Node",
"Alias": "End"
}
],
"Transitions": [
{
"From": "Start",
"To": "Process"
},
{
"From": "Process",
"To": "End"
}
]
}
Extension Development Guide
Inheritance Hierarchy
Activity (SYC.Flow.Kernel)
└── BusinessActivity
├── ActionActivity (Action Node Base Class)
│ ├── ApiCallActivity
│ ├── MethodCallActivity
│ └── ScriptCallActivity
├── ProcessBusinessActivity (Process Node Base Class)
│ ├── GenerateCodeActivity
│ ├── MaterialDetectionActivity
│ └── PrintLabelActivity
└── Other Custom Nodes
Overridable Methods
| Method Name | Purpose | When to Override |
|---|---|---|
| CanEnterAsync | Determine if node can be entered | Need custom entry conditions (e.g., check prerequisites) |
| EnterAsync | Executed when entering node | Need initialization operations (e.g., initialize variables, create resources) |
| ProcessAsync | Core business logic | Must Override, implement specific business functionality |
| CanExitAsync | Determine if node can be exited | Need custom exit conditions (e.g., verify processing results) |
| ExitAsync | Executed when exiting node | Need cleanup operations (e.g., release resources, save state) |
| Validate | Validate node configuration | Need custom configuration validation logic |
Custom Node Development Steps
- Create Node Class
[Serializable]
[Design("Node Name", "Node Description", Sort = Sort Order)]
[Category("Category Name")]
public class MyCustomActivity : BusinessActivity
{
// Implementation code
}
- Add Configuration Properties
[Design("Property Name", "Property Description", Sort = Sort Order)]
[Category("Category Name")]
[DataMember]
[Required] // Optional: Mark as required
public string MyProperty { get; set; }
- Define Flow Context Keys
public override List<FlowItemKey> FlowItemKeys => new()
{
new FlowItemKey("MyInput_Value", "Input", "Input Description", typeof(string)),
new FlowItemKey("MyOutput_Value", "Output", "Output Description", typeof(string))
};
- Implement Business Logic
public override async Task ProcessAsync(ProcessflowEventArgs args)
{
// 1. Read input
var input = GetFlowItemValue("MyInput")?.ToString();
// 2. Process business
var result = await DoBusinessLogic(input);
// 3. Write output
Flow.DataItems["MyOutput_Value"] = result;
// 4. Record log
Flow.Logger.LogMessage($"Processing complete", Name);
await base.ProcessAsync(args);
}
Registration and Integration
-
Compile Node:
- Compile custom node into DLL
- Ensure necessary dependency packages are referenced
-
Deploy Node:
- Place DLL in LMES plugin directory
- Or integrate directly into main project
-
Configure Node:
- New node visible in flow designer
- Information configured via Design attribute will be displayed in designer
-
Test Node:
- Create test flow
- Run flow to verify node functionality
- Check log output
Notes
- ⚠️ Async Methods: All lifecycle methods are asynchronous, must use async/await correctly
- ⚠️ Exception Handling: Exceptions in ProcessAsync are automatically captured, but proper handling should still be done
- ⚠️ Flow Cancellation: Long-running operations should check
Flow.CancellationTokenSource.Token - ⚠️ Thread Safety: Nodes may execute in multi-threaded environments, pay attention to thread safety
- ⚠️ Performance: Avoid executing time-consuming synchronous operations in ProcessAsync
- 💡 Best Practices:
- Use GetFlowItemValue instead of accessing Flow.DataItems directly to support complex paths
- Make full use of logging for easy troubleshooting
- Use extended properties reasonably to improve node flexibility
- Validate input parameters at the beginning of ProcessAsync
Related Nodes
- ActionActivity: Inherits from BusinessActivity, adds execution timing control
- EndActivity: Flow end node
- ParallelStart: Parallel flow start node
- ParallelEnd: Parallel flow end node
- SubflowActivity: Subflow node
FAQ
Q1: How to pass data between nodes?
A: Use flow context (Flow.DataItems):
// Write data
Flow.DataItems["MyKey_Value"] = myValue;
// Read data
var value = Flow.DataItems["MyKey_Value"];
Q2: How to access injected services?
A: Use Flow.ServiceProvider:
var myService = Flow.ServiceProvider.GetRequiredService<IMyService>();
Q3: How to handle node execution failure?
A: Throw an exception in ProcessAsync, the system will handle it automatically:
if (!success)
{
throw new BusinessException("MyNode", "Reason for failure");
}
Q4: How to dynamically disable a node?
A: Set the disabled flag in the flow context:
Flow.DataItems["MyNode_IsActivityDisabled"] = true;
Update History
| Date | Version | Description |
|---|---|---|
| 2025-11-28 | 1.0 | Initial Release |
Last updated: 2025-11-28