Recursive AI

Call a JavaScript function from C#

All oneecma recipes

Evaluate the script that defines the function, read the function from the global object with GetProperty, and call it with engine.Call. The program below calls a pricing function from a script with C# arguments.

Terminal
dotnet new console -o CallJs && cd CallJs
dotnet add package RecursiveAI.OneEcma --version 1.0.0

Put this in Program.cs and run dotnet run:

C#
using RecursiveAI.OneEcma;
 
var engine = new ScriptEngine();
engine.Evaluate("""
    function price(qty, unit, code) {
      if (qty <= 0) throw new RangeError(`qty must be positive, got ${qty}`);
      const discount = code === "SPRING" ? 0.1 : 0;
      return Math.round(qty * unit * (1 - discount) * 100) / 100;
    }
    """, "pricing.js");
 
JsValue price = engine.GetProperty(engine.Global, "price");
 
JsValue a = engine.Call(price, default,
    [JsValue.FromNumber(12), JsValue.FromNumber(2.5), JsValue.FromString("SPRING")]);
JsValue b = engine.Call(price, default,
    [JsValue.FromNumber(3), JsValue.FromNumber(9.99), JsValue.FromString("")]);
Console.WriteLine($"{a.AsNumber} and {b.AsNumber}");
 
try
{
    engine.Call(price, default,
        [JsValue.FromNumber(0), JsValue.FromNumber(1), JsValue.FromString("")]);
}
catch (JsException e)   // what the script threw, as a .NET exception
{
    Console.WriteLine(e.Message);
}
Text
27 and 29.97
RangeError: qty must be positive, got 0

default is the this value, here undefined. Arguments are JsValues: FromNumber, FromString, FromBoolean and FromObject make them. An exception thrown in the script comes back as a JsException with the script’s message, and e.Report holds a full report with the line that threw.

Calling it many times

Evaluate the script once and keep the engine and the function: each Call runs only the function. Pass a CancellationToken as the last argument of Call to stop a call that takes too long, or set limits on the engine.

Limits

  • One engine per thread. Calls into the same engine must come from one thread at a time.
  • Async functions return a promise. Call engine.RunJobs() to run pending jobs, then read the promise with engine.MainRealm.AsPromise(value).
  • Modules: a function exported from an ES module isn’t on the global object. Load modules with EvaluateModule and a host that resolves them (Runtime and the host).

To run a script and read its result without a function, see Run JavaScript in C#. To go the other way, see Call C# from JavaScript.