Skip to main content

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 executeCommonProgram API to client-side widgets or external callers.

Applicable Program Types

IProgramService.ExecuteCommonProgramAsync can be called from any program type:

CallerNotes
PostCreate / PostUpdateMost common; called after instance changes
PromoteActionCommandCompute values or trigger side effects on state transition
SchedulerRun batch computations by calling common programs per instance
Another Common ProgramPrograms 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:

ResultReturn value
Single valuenew object[] { value }
Multiple valuesnew object[] { value1, value2, ... }
No data (success)new object[] { }
Error (let caller handle)Throw an Exception

Key Services

ServiceMethodPurpose
IProgramServiceExecuteCommonProgramAsync(name, args)Invoke a common program by class name
IProgramServiceExecuteCommonProgram(name, args)Synchronous version (deprecated)