Run JavaScript in .NET using C#
Since Microsoft has decided to use Google’s browser engine for Edge, Google’s V8 JavaScript engine has become the de-facto standard.
As we at e.GO Mobile work a lot with this engine (see Node.js), I would like to demonstrate today how to bring both worlds together.
In the following article, I want to show how to integrate this engine into your C# programs and interact between both worlds.
Requirements
The only requirement we have is a running .NET SDK, which can be downloaded and installed from here.
Create project
Open your terminal application and create a new folder, lets say my-cli-project
, change to it and execute
dotnet new console --use-program-main
to setup a basic hello world application.
One note: I always recommend using –use-program-main flag because it can be difficult for beginners to understand how the new implicit functions work in .NET and C#, as references such as imported namespaces are not immediately visible.
Check with
dotnet run
if anything works fine and you get the expected output.
Add required package
Microsoft implemented a CLR package, which is called ClearScript.
One feature is, that it has bindings to the V8 JavaScript engine of Google.
To install this nuget package, open the project in your terminal and execute
dotnet add package Microsoft.ClearScript.Complete
to install the latest version.
Run first script
First cleanup the Program.cs
file with this skeleton:
using System;
namespace MyCliProject;
class Program
{
async static Task<int> Main(string[] args)
{
return 0;
}
}
Next we need to import the Microsoft.ClearScript.V8
namespace, where we can access the V8ScriptEngine class.
Their objects will later make it possible to communicate between .NET and JavaScript environment:
// ...
using Microsoft.ClearScript.V8;
// ...
async static Task<int> Main(string[] args)
{
var engine = new V8ScriptEngine();
var scriptCode = "console.log('Hello, world!');";
engine.Execute(scriptCode);
// ...
}
// ...
Note: If we now execute dotnet run
, the application is executed but there is no output. The console
inside engine
seems to have no connection to the STDOUT of the running application.
What we can do is that we import the Console class from .NET:
using System;
// ...
async static Task<int> Main(string[] args)
{
var engine = new V8ScriptEngine();
// add types
engine.AddHostType("Console", typeof(global::System.Console));
// add objects and values
engine.AddHostObject("myMessage", "Hello, world!");
var scriptCode = "Console.WriteLine(myMessage);";
engine.Execute(scriptCode);
// ...
}
// ...
This code update should show a Hello, world!
output.
With AddHostType() and AddHostObject() methods we can do this with any type or object we have access to.
Some basics
Access members
Inside the V8 engine you can access any public member of imported classes and objects:
// `url` is an instance from `System.Uri`
const urlQueryString = url.Query;
const urlAsString = url.ToString();
LINQ
Another nice feature is to use LINQ more or less out-of-the-box.
What you have to do is to import the Enumerable class:
// ...
async static Task<int> Main(string[] args)
{
// ...
engine.AddHostType("Enumerable", typeof(global::System.Linq.Enumerable));
// ...
}
Lets say you have also imported Int32 struct, which is the .NET class of int
shorthand in C#, you can do things like this:
const rangeOfInts = Enumerable.Empty(Int32) // create empty sequence of ints
.Concat(Enumerable.Range(0, 10)) // add numbers between 0 and 9
.ToArray();
// as we have an .NET array the property
// is `Length` and not `length`
for (let i = 0; i < rangeOfInts.Length; i++) {
Console.WriteLine(i);
}
Output parameters
If you have an instance of a class like Dictionary and need to use its TryGetValue() method that requires an output parameter, you first have to import a HostFunctions instance:
// ...
using System.Collections.Generic;
using Microsoft.ClearScript;
// ...
async static Task<int> Main(string[] args)
{
// ...
var myDict = new Dictionary<string, string>();
myDict["testKey1"] = "Test value";
engine.AddHostObject("host", new HostFunctions());
engine.AddHostObject("myDict", myDict);
engine.AddHostType("String", typeof(global::System.String));
// ...
}
// ...
In the script you can create a new “output variable” via host
and submit it to the dictionary’s method:
const outVar = host.newVar(String);
if (dict.TryGetValue('testKey1', outVar.out)) {
Console.WriteLine('found string value in "testKey1": {0}', outVar);
}
if (dict.TryGetValue('testKey2', outVar.out)) {
Console.WriteLine('found string value in "testKey2": {0}', outVar);
}
Callbacks
Lets say you want to use a timer, like setInterval() function, you can make use of Timer class:
// ...
async static Task<int> Main(string[] args)
{
// ...
// add types
engine.AddHostType("Timer", typeof(global::System.Threading.Timer));
engine.AddHostType("TimerCallback", typeof(global::System.Threading.TimerCallback));
// ...
}
In JavaScript it can look like this:
const timerCallback = new TimerCallback(function (state) {
Console.WriteLine('Timer fired: {0}', state);
});
// `host` is an imported instance of `HostFunctions` class (s. above)
const timer = host.newObj(Timer, timerCallback, 'foo', 5000, 5000);
Access objects and functions from script
You can simple to an export the other way round:
Lets say you have the following function:
function myScriptFunc(a, b) {
return {
aVal: String(a).toUpperCase().trim(),
bVal: String(b).toUpperCase().trim(),
};
}
With Script property you are able to access and execute it from your C# code:
// ...
async static Task<int> Main(string[] args)
{
// ...
var resultA = engine.Script.myScriptFunc("The A", "the b").aVal;
var resultB = engine.Script.myScriptFunc("The A", "the b").bVal;
// ...
}
Conclusion
In my opinion this is a very nice way to execute JavaScript code from C# and interact between both, the .NET and the V8 world!
ClearScript
is not only able to use the V8 engine, it can also make use of JScript and VBScript as well.
If you want to go into more detail, the official ClearScript FAQtorial is quite helpful.
Have fun while trying it out! 🎉