Recursive AI

Run JavaScript in C#

All oneecma recipes

Create a ScriptEngine and call Evaluate: it runs the script and returns the value of its last expression. The engine is pure C#, so there is no Node.js, V8 or other native library to install.

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

Put this in Program.cs and run dotnet run:

C#
using System.Text.Json;
using RecursiveAI.OneEcma;
 
var engine = new ScriptEngine();
 
// The value of the last expression comes back as a JsValue.
JsValue sum = engine.Evaluate("[1, 2, 3, 4].reduce((a, b) => a + b, 0)");
Console.WriteLine($"sum: {sum.AsNumber}");
 
// Pass a value in by putting it on the global object.
engine.SetProperty(engine.Global, "name", JsValue.FromString("Ada"));
JsValue greeting = engine.Evaluate("`Hello, ${name.toUpperCase()}!`");
Console.WriteLine(greeting.AsString);
 
// Globals and functions stay defined for the next Evaluate.
engine.Evaluate("""
    function invoice(items) {
      const total = items.reduce((t, i) => t + i.qty * i.price, 0);
      return { count: items.length, total: Math.round(total * 100) / 100 };
    }
    """, "invoice.js");
 
// Objects: return JSON from the script and parse it in C#.
JsValue json = engine.Evaluate(
    "JSON.stringify(invoice([{ qty: 3, price: 1.5 }, { qty: 2, price: 4.25 }]))");
using JsonDocument doc = JsonDocument.Parse(json.AsString);
Console.WriteLine($"items: {doc.RootElement.GetProperty("count")}, " +
    $"total: {doc.RootElement.GetProperty("total")}");
Text
sum: 10
Hello, ADA!
items: 2, total: 13

Evaluate takes a file name as its second argument; error reports and err.stack use it. A script that throws reaches C# as a JsException whose Message is what the script would see (TypeError: …), with a readable report on Report.

Reading values

A JsValue is any JavaScript value. AsNumber, AsString, AsBoolean and AsObject read it as that type, IsUndefined checks for undefined, and engine.ToDisplayString(value) converts anything to a string the way JavaScript’s String(value) does. For arrays and objects, JSON.stringify in the script and System.Text.Json in C#, as above, is the shortest path; engine.GetProperty(obj, "name") reads one property at a time.

Limits

  • The language and its built-ins only. There is no require, fetch, process, file system or console unless you provide them: see Call C# from JavaScript.
  • One engine per thread. A ScriptEngine isn’t thread-safe; create one per thread or per script.
  • No time limit by default. A script that loops forever runs forever. For code you didn’t write, set limits: see Run untrusted JavaScript safely.
  • .NET 10. The package targets net10.0.

To call a function defined in a script with arguments from C#, see Call a JavaScript function from C#. The full tour is Getting started.