Flow Studio
IProcessElementExecution
Every node executor implements IProcessElementExecution. The interface defines four methods: ExecuteAsync, ValidateAsync, HandleErrorAsync, and CleanupAsync. In practice, you only implement ExecuteAsync — the others are handled by BaseNodeExecutor.
The Full Interface
// BizFirst.Ai.ProcessEngine.Domain/Node/Interfaces/IProcessElementExecution.cs
public interface IProcessElementExecution
{
/// Execute this node with the given context and return a result.
Task<NodeExecutionResult> ExecuteAsync(
NodeExecutionContext nodeExecutionContext,
CancellationToken cancellationToken = default);
/// Validate configuration before the workflow runs (pre-flight check).
Task<ValidationResult> ValidateAsync(
ProcessElementValidationContext validationContext,
CancellationToken cancellationToken = default);
/// Handle an exception that occurred during ExecuteAsync.
Task<NodeExecutionResult> HandleErrorAsync(
ProcessElementExecutionContext executionContext,
Exception error,
CancellationToken cancellationToken = default);
/// Release resources after execution (successful or failed).
Task CleanupAsync(
ProcessElementExecutionContext executionContext,
CancellationToken cancellationToken = default);
}
Method Responsibilities
| Method | Called When | Your Responsibility |
|---|---|---|
ExecuteAsync |
Engine reaches this node during execution | Implement your node's logic here. Must return NodeExecutionResult. |
ValidateAsync |
Designer validates the workflow before saving or running | BaseNodeExecutor handles this — parses config and calls settings.Validate(). Override settings.Validate() for custom rules. |
HandleErrorAsync |
An unhandled exception is thrown in ExecuteAsync |
BaseNodeExecutor.ErrorHandler handles this — routes to error port or rethrows. Rarely overridden. |
CleanupAsync |
After execution completes (success or failure) | No-op by default in BaseNodeExecutor. Override if you hold resources (DB connections, file handles). |
TypeCode Property
The interface does not declare a TypeCode property, but the convention (enforced by the ExecutorRegistry) is that each executor class exposes a public const string NodeTypeName or a public override string ProcessElementTypeCode:
public partial class MyCustomNodeExecutor : BaseNodeExecutor
{
// Used as the registry key AND as the DB NodeType.typeCode
public const string NodeTypeName = "my-custom-node";
public override string ProcessElementTypeCode => NodeTypeName;
}
Do Not Implement IProcessElementExecution Directly
Always extend
BaseNodeExecutor instead. Implementing the interface directly means you must write all the error handling, configuration loading, GuardRails integration, and SignalR event emission yourself. BaseNodeExecutor provides all of this out of the box.