Invoke Common Program Pattern
Overview
Common programs encapsulate reusable business logic that can be called from other programs using IProgramService.ExecuteCommonProgramAsync. They return an object[] result that the caller casts and interprets. This pattern enables code reuse across triggers, schedulers, and other common programs — and avoids duplicating complex business logic.
When to Use
- The same calculation is needed in multiple programs (e.g., "Calculate overall status" called from both a PostCreate trigger and a Scheduler).
- A complex operation must be composed from smaller, independently testable steps.
- Exposing custom logic via the
executeCommonProgramAPI to client-side widgets or external callers.
Applicable Program Types
IProgramService.ExecuteCommonProgramAsync can be called from any program type:
| Caller | Notes |
|---|---|
| PostCreate / PostUpdate | Most common; called after instance changes |
| PromoteActionCommand | Compute values or trigger side effects on state transition |
| Scheduler | Run batch computations by calling common programs per instance |
| Another Common Program | Programs can chain to each other |
Defining a Common Program
A common program implements ICommonProgramAsync with a RunAsync(params Object[] arguments) method.
using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace YourNamespace.Programs
{
/// <summary>
/// Type: Common Program
/// Summary: Calculates the compliance score for a given project.
/// Input: arguments[0] = projectId (Guid)
/// Output: object[] { "Yes" | "No" | "NA" }
/// </summary>
public class CalculateComplianceScore : ICommonProgramAsync
{
public IInstanceService InstanceService { get; set; }
private readonly string ATTRIBUTE_COMPLIANCE_STATUS = "ComplianceStatus";
public async Task<Object> RunAsync(params Object[] arguments)
{
// 1. Validate input
if (arguments == null || arguments.Length < 1 || arguments[0] == null)
throw new ArgumentException("projectId is required as arguments[0]");
if (!Guid.TryParse(arguments[0].ToString(), out Guid projectId))
throw new ArgumentException("arguments[0] must be a valid Guid (projectId)");
// 2. Execute business logic
var project = await InstanceService.GetInstanceAsync(projectId, new[] { ATTRIBUTE_COMPLIANCE_STATUS });
var result = project.GetPicklistAttributeValue(ATTRIBUTE_COMPLIANCE_STATUS)?.FirstOrDefault() ?? "NA";
// 3. Return result as object[]
return new Object[] { result };
}
}
}
Invoking a Common Program from Another Program
Calls two common programs and combines their results to calculate an overall status.
using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Contracts.Enum;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Threading.Tasks;
using Attribute = Prorigo.Protrak.API.Contracts.Attribute;
namespace YourNamespace.Programs
{
/// <summary>
/// Type: Common Program
/// Summary: Orchestrates overall status calculation by calling CalculateComplianceScore
/// and GetStatusByEscalations, then sets the combined result on the project.
/// </summary>
public class CalculateOverallStatus : ICommonProgramAsync
{
public IProgramService ProgramService { get; set; }
public IInstanceService InstanceService { get; set; }
public ILoggingService LoggingService { get; set; }
private readonly string TYPE_PROJECT = "Project";
private readonly string ATTRIBUTE_STATUS = "OverallStatus";
private readonly string COMMON_COMPLIANCE = "CalculateComplianceScore";
private readonly string COMMON_ESCALATION = "GetStatusByEscalations";
public async Task<Object> RunAsync(params Object[] arguments)
{
try
{
if (arguments.Length == 0)
throw new ArgumentException("projectId is required as arguments[0]");
var projectId = Guid.Parse(arguments[0].ToString());
var input = new object[] { projectId };
// Call sub-programs
var complianceResult = (object[]) await ProgramService.ExecuteCommonProgramAsync(COMMON_COMPLIANCE, input);
var complianceScore = complianceResult[0].ToString(); // "Yes" | "No" | "NA"
var escalationResult = (object[]) await ProgramService.ExecuteCommonProgramAsync(COMMON_ESCALATION, input);
var escalationStatus = escalationResult[0].ToString(); // "Red" | "Amber" | "Green"
// Combine results
string finalStatus = escalationStatus == "Red" ? "Red"
: escalationStatus == "Amber" ? "Amber"
: complianceScore == "No" ? "Amber"
: "Green";
// Update the project
await UpdateStatusAsync(projectId, finalStatus);
return new Object[] { };
}
catch (Exception ex)
{
LoggingService.Error("CalculateOverallStatus error: " + ex.Message);
throw;
}
}
private async Task UpdateStatusAsync(Guid projectId, string status)
{
var updated = new Instance { Id = projectId, InstanceTypeName = TYPE_PROJECT };
updated.SetPicklistAttributeValue(ATTRIBUTE_STATUS, status);
await InstanceService.UpdateInstanceAsync(updated, null);
}
}
}
Calling a Common Program from a PostCreate Trigger
Dispatches to a common program based on a discriminator attribute on the triggering instance.
public async Task RunAsync(Guid instanceId)
{
var systemEvent = await InstanceService.GetInstanceAsync(instanceId,
new[] { "InstanceType", "ProgramData", "Error" });
var instanceType = systemEvent.GetTextAttributeValue("InstanceType");
var programData = systemEvent.GetTextAttributeValue("ProgramData");
if (string.IsNullOrEmpty(instanceType)) return;
try
{
await ProgramService.ExecuteCommonProgramAsync(instanceType, new object[] { programData });
await InstanceService.PromoteInstanceAsync(instanceId, "MarkAsCompleted", "Done");
}
catch (Exception ex)
{
systemEvent.SetTextAttributeValue("Error", ex.Message);
await InstanceService.UpdateInstanceAsync(systemEvent, null);
await InstanceService.PromoteInstanceAsync(instanceId, "MarkAsFailed", "Error occurred");
}
}
Receiving Arguments from the executeCommonProgram API
When a common program is called from a client via the POST /programs/{programName}/executeCommonProgram endpoint, arguments[0] is a JsonElement. Parse it explicitly:
using System.Text.Json;
public async Task<Object> RunAsync(params Object[] arguments)
{
if (arguments == null || arguments.Length < 1)
throw new InvalidDataException("Invalid input: arguments are required.");
var element = (JsonElement)arguments[0];
if (!element.TryGetProperty("instanceId", out JsonElement idElement) ||
!Guid.TryParse(idElement.GetString(), out Guid instanceId))
{
throw new KeyNotFoundException("instanceId (Guid) is required in the request payload.");
}
// ... use instanceId
return new Object[] { "result" };
}
Return Value Convention
All common programs return Object (cast to object[] by callers). The convention is:
| Result | Return value |
|---|---|
| Single value | new object[] { value } |
| Multiple values | new object[] { value1, value2, ... } |
| No data (success) | new object[] { } |
| Error (let caller handle) | Throw an Exception |
Key Services
| Service | Method | Purpose |
|---|---|---|
IProgramService | ExecuteCommonProgramAsync(name, args) | Invoke a common program by class name |
IProgramService | ExecuteCommonProgram(name, args) | Synchronous version (deprecated) |