9.3 KiB
Scripts: JavaScript & C#
Elsa can run code inside a workflow two ways:
- Script activities —
Run JavaScript(Elsa.RunJavaScript) andRun C#(Elsa.RunCSharp). Both take aScriptinput and produce aResultoutput. - Inline expressions — any activity input can hold an expression instead of
a literal. The syntax is chosen per value:
JavaScript,CSharporLiquid.
Both routes evaluate against the current execution context, so they can read workflow inputs, read/write workflow variables and read the outputs of activities that already ran. This page lists exactly what is callable.
JavaScript runs on the Jint engine. C# runs on Roslyn scripting. The lists below are taken from the engine source (Elsa
3.8.2,elsa-core/src/modules/Elsa.Expressions.JavaScriptandelsa-core/src/modules/Elsa.Expressions.CSharp). Only what is listed here is wired by this host — in particular JS has noconsole, no CLR access and nogetConfig(see Limits).
Reading and writing data
| What you want | JavaScript | C# |
|---|---|---|
| Read a workflow variable | getVariable("Name") |
ExecutionContext.GetVariable("Name") / Variable.Get("Name") |
| Write a workflow variable | setVariable("Name", value) |
ExecutionContext.SetVariable("Name", value) / Variable.Set("Name", value) |
| Variable as a property | variables.Name |
Variable.Name (typed, see note) |
| Read a workflow input | getInput("name") |
Input.Get<T>("name") |
| Output of an earlier activity | getOutputFrom("Activity", "Result") |
Output.From("Activity", "Result") / Output.From<T>(...) |
| Result of the previous activity | getLastResult() |
Output.LastResult |
| Current workflow instance id | getWorkflowInstanceId() |
WorkflowInstanceId |
| Correlation id | getCorrelationId() / setCorrelationId(v) |
CorrelationId (get/set) |
| Workflow instance name | getWorkflowInstanceName() / setWorkflowInstanceName(v) |
WorkflowInstanceName (get/set) |
| Definition id / version | getWorkflowDefinitionId(), getWorkflowDefinitionVersionId(), getWorkflowDefinitionVersion() |
— (use the workflow variables/inputs instead) |
Notes:
getVariable/Variable.Getare exact-name. The typed C# propertyVariable.Nameis generated from the Pascalized variable name and looks the variable up by that Pascalized name, so useVariable.Get("exactName")when a variable is not already PascalCase.- The activity reference in
getOutputFrom/Output.Fromis the activity's name or id (name is what the designer shows). The output name is the one from the activity descriptor —Resultfor code activities such asW4C.W4cEcho,W4C.RunSql,Elsa.RunJavaScript. - Reading an output of an activity that has not run yet returns
null.
JavaScript API
Built-in functions
Workflow / context:
| Function | Returns |
|---|---|
getWorkflowDefinitionId() |
definition id (string) |
getWorkflowDefinitionVersionId() |
definition version id (string) |
getWorkflowDefinitionVersion() |
version number (int) |
getWorkflowInstanceId() |
workflow instance id (string) |
getCorrelationId() / setCorrelationId(value) |
correlation id |
getWorkflowInstanceName() / setWorkflowInstanceName(value) |
instance name |
getInput(name) |
workflow input value |
getVariable(name) / setVariable(name, value) |
variable value |
variables.<name> |
variable value (write-back supported) |
getOutputFrom(activity, outputName) |
output of another activity |
getLastResult() |
result of the previous activity |
Helpers:
| Function | Purpose |
|---|---|
toJson(value) |
JSON string (enums as strings, full Unicode) |
isNullOrEmpty(value) / isNullOrWhiteSpace(value) |
string checks |
parseGuid(value) |
Guid from string |
newGuid() / newGuidString() / newShortGuid() |
new Guids |
bytesToString / bytesFromString |
UTF-8 byte ↔ string |
bytesToBase64 / bytesFromBase64 |
byte[] ↔ base64 |
stringToBase64 / stringFromBase64 |
string ↔ base64 |
streamToBytes(stream) / streamToBase64(stream) |
Stream → bytes/base64 |
Outcomes (only meaningful in Run JavaScript, whose Script input supports
them): setOutcome(name) and setOutcomes(names[]). If neither is called the
activity completes with the default outcome Done. Declare the possible
names in the activity's PossibleOutcomes input so they appear on the node.
Deprecated but present: getGuidString(), getShortGuid().
Generated accessors
For convenience the engine also injects name-based accessors:
- Variables:
get<Name>()/set<Name>(value)where<Name>is the variable name Pascalized (counter→getCounter()). These use the real variable name internally, so they are reliable for any variable name. - Workflow inputs:
get<inputName>()— the accessor keeps the exact input name (name→getname()). - Activity outputs:
get<Output>From<ActivityName>(), both Pascalized (Resultfrom activityEcho→getResultFromEcho()).
The explicit functions above are the stable API; the accessors are a shorthand.
Registered .NET types
DateTime, DateTimeOffset, TimeSpan, Guid, Random,
LogPersistenceMode, plus the types of the workflow's declared variables. They
are usable as static classes, e.g. DateTime.UtcNow, Guid.NewGuid().
Values and results
- The value of the last expression, or an explicit top-level
return(returnoutside a function is allowed), becomes the script result. - A returned Promise is awaited/unwrapped by the engine.
- If the result is not
null,Run JavaScriptwrites it to theResultoutput.
// totals an order, stores it in a variable and returns a JSON summary
const order = getInput("order");
const total = order.items.reduce((sum, item) => sum + item.price, 0);
setVariable("Total", total);
const echo = getOutputFrom("Echo", "Result"); // result of an earlier node
if (total > 1000) setOutcome("Rejected"); else setOutcome("Approved");
return toJson({ total, echo });
C# API
The script is a Roslyn C# script. Its globals are an instance of
Elsa.Expressions.CSharp.Models.Globals, which exposes:
| Global | Type | Use |
|---|---|---|
ExecutionContext |
ExecutionContextProxy |
GetVariable(name), GetVariable<T>(name), SetVariable(name, value), GetInput<T>(name), and .ExpressionExecutionContext for the raw context |
Variable (alias Variables) |
WorkflowVariablesProxy |
Get(name), Get<T>(name), Set(name, value) + a typed property per variable |
Input (alias Inputs) |
WorkflowInputsProxy |
Get<T>(name) + a typed property per workflow input |
Output |
OutputProxy |
From(activity, outputName), From<T>(...), LastResult |
Outcome |
OutcomeProxy |
Set(params string[] outcomeNames) |
Args |
ArgumentsProxy |
arguments passed by the caller of the evaluator (Run C# passes none — advanced/internal) |
WorkflowInstanceId |
string |
workflow instance id |
CorrelationId |
string? (get/set) |
correlation id |
WorkflowInstanceName |
string? (get/set) |
instance name |
Arguments |
IDictionary<string, object> |
raw argument dictionary |
Result and outcomes:
- The script's last expression /
returnvalue becomes the script result; when notnullit is written to theRun C#Resultoutput. - Call
Outcome.Set("Approved")(orOutcome.Set("Approved", "Rejected")) to choose an outcome; the default isDone. Declare them inPossibleOutcomes. awaitworks in the script.
Imports and references:
- Default namespaces:
Elsa.Expressions.CSharp,System,System.Linq,System.Collections.Generic,System.Dynamic,System.Text.Json,System.Text.Json.Serialization,System.Text.Json.Nodes. - Default assemblies: Elsa.Expressions.CSharp, System.Linq/System.Runtime,
System.Text.Json, System.Collections, System.Linq.Expressions
(
ExpandoObject) and Microsoft.CSharp (dynamic). Nothing else (noSystem.Net.Http, noSystem.IO) unless the host adds it.
var order = Input.Get<Order>("order");
var total = order.Items.Sum(item => item.Price);
Variable.Total = total; // typed var (PascalCase)
ExecutionContext.SetVariable("Total", total); // exact-name API
var echo = Output.From("Echo", "Result");
Outcome.Set(total > 1000 ? "Rejected" : "Approved");
return JsonSerializer.Serialize(new { total, echo });
Limits
- JavaScript is the safe sandbox. This host calls
UseJavaScript()without options, soAllowClrAccessandAllowConfigurationAccessstayfalse: there is noclr, nogetConfig, and noconsole. The extra JS libraries (lodash, moment) are not installed. The only extension points are the registered types and the built-ins listed above. - C# is not a sandbox.
Scripting:CSharp:AllowHostCodeExecutionistruein this deployment (including production), soRun C#can run arbitrary code in the Elsa host process — filesystem, network, DI services and every tenant's data. Only trusted authors should be allowed to edit flows. Turning the flag off removesRun C#and theCSharpsyntax from the palette entirely. - Both engines cache compiled scripts for one day (sliding), and both run in-process with the caller's cancellation token.