Skip to main content

Promote Action Program (IPromoteActionProgramAsync)

Overview

A Promote Action Program in Protrak implements the IPromoteActionProgramAsync interface to provide custom logic for lifecycle state transitions. These programs are invoked during promotions (state changes) and can be used to dynamically determine the next state based on instance data or to enforce complex business rules or validations.

Interface

public interface IPromoteActionProgramAsync
{
Task<string> RunAsync(Guid instanceId, string actionName);
}
  • RunAsync: Executes custom logic during a workflow promotion. Returns the name of the target state to which the instance should be promoted. Throws an exception to block the promotion if business rules are not satisfied.

Coding Guidelines

  • Implement IPromoteActionProgramAsync with the RunAsync method to enable asynchronous, scalable trigger logic.
  • Most internal service methods are now async, and their synchronous versions are deprecated and will be removed. Use RunAsync to ensure compatibility with these changes.
  • Always validate that the instance exists before performing any logic.
  • Use InstanceService.GetInstanceAsync to retrieve the instance and required attributes.
  • Perform all necessary business rule checks and throw descriptive exceptions for invalid scenarios.
  • Return the name of the target state to which the instance should be promoted.
  • Avoid hardcoding state names and picklist values; use constants for maintainability.
  • Keep the logic focused on a single responsibility (e.g., validation, state determination).
  • Do not perform side effects (notifications, updates) in Promote Action Programs—use triggers or commands for those.

Use Cases

  • Enforcing business rules before allowing a state transition (e.g., required approvals, data completeness).
  • Dynamically determining the next state based on instance attributes or user input.
  • Blocking promotions if certain conditions are not met (e.g., missing recommendations, incomplete data).
  • Supporting complex workflow branching logic.

Anti-Patterns

  • Performing unrelated side effects (e.g., sending notifications, updating unrelated data).
  • Returning ambiguous or empty state names.
  • Swallowing exceptions or returning error messages as state names.
  • Duplicating business logic that should be shared across multiple programs.

Sample Code

Example: Dynamic State Determination Based on Approval

public class ApproveOrRejectPromotion : IPromoteActionProgramAsync
{
public IInstanceService InstanceService { get; set; }

private readonly string ATTRIBUTE_APPROVAL_DECISION = "ApprovalDecision";
private readonly string PICKLIST_VALUE_APPROVE = "Approve";
private readonly string PICKLIST_VALUE_REJECT = "Reject";
private readonly string STATE_APPROVED = "Approved";
private readonly string STATE_REJECTED = "Rejected";

public async Task<string> RunAsync(Guid instanceId, string actionName)
{
string[] attributes = new string[] { ATTRIBUTE_APPROVAL_DECISION };
var instance = await InstanceService.GetInstanceAsync(instanceId, attributes);
if (instance == null)
throw new Exception("Instance does not exist");

var approvalDecision = instance.Attributes.FirstOrDefault(a => a.Name == ATTRIBUTE_APPROVAL_DECISION);
if (approvalDecision == null || approvalDecision.ArrayValue == null)
throw new Exception("Approval decision is required before promotion.");

string targetStateName = string.Empty;
if (approvalDecision.ArrayValue[0] == PICKLIST_VALUE_APPROVE)
{
targetStateName = STATE_APPROVED;
}
else if (approvalDecision.ArrayValue[0] == PICKLIST_VALUE_REJECT)
{
targetStateName = STATE_REJECTED;
}

if (string.IsNullOrWhiteSpace(targetStateName))
throw new Exception("Approval decision must be Approve or Reject.");

return targetStateName;
}
}

Example: Promotion Blocked by Missing Reviewer

public class RequireReviewerBeforePromotion : IPromoteActionProgramAsync
{
public IInstanceService InstanceService { get; set; }
private readonly string ATTRIBUTE_REVIEWER = "Reviewer";
private readonly string STATE_UNDER_REVIEW = "Under Review";

public async Task<string> RunAsync(Guid instanceId, string actionName)
{
var instance = await InstanceService.GetInstanceAsync(instanceId, new[] { ATTRIBUTE_REVIEWER });
var reviewer = instance.Attributes.FirstOrDefault(a => a.Name == ATTRIBUTE_REVIEWER);
if (reviewer == null || string.IsNullOrWhiteSpace(reviewer.TextValue))
throw new Exception("Reviewer must be assigned before promotion.");
return STATE_UNDER_REVIEW;
}
}

Note: The Run method (from IPromoteActionProgram) should only be used if the trigger logic is fully synchronous, does not involve any service/API calls, and consists only lightweight in-memory operations.

Troubleshooting

  • If the returned state name does not match a valid workflow state, the promotion will fail.
  • Always throw exceptions for business rule violations to block invalid promotions.
  • Use clear and actionable exception messages for end users.
  • If the program is not invoked, check workflow configuration and action mappings.
  • Avoid returning null or empty strings as state names.