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.
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:
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")}");
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 orconsoleunless you provide them: see Call C# from JavaScript. - One engine per thread. A
ScriptEngineisn’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.