Running JavaScript in .NET Applications

Running JavaScript in .NET Applications

The other day I was working on a side project and realized I needed to use some JavaScript functionality.

Last updated 5/11/2022 6:51 AM
liamwang 精致码农
11 min read
Category
.NET
Tags
.NET C#

A few days ago, I was working on a side project and realized I needed to use some JavaScript functionality. The thought of dealing with Node.js and npm again made me give up entirely, so I decided to investigate the possibility of running JavaScript inside a .NET application. Crazy, right? Actually, it turned out to be surprisingly simple.

1. Why Would You Do This?

Although I love the .NET ecosystem, there are some things that the JavaScript ecosystem does better. One of them is finding a library for anything, especially when it comes to the web.

Take syntax highlighting as an example. This could be done directly in C#, but it's not a particularly smooth experience. For instance, the TextMateSharp project provides an interpreter for TextMate grammars. These files are what VS Code uses to add basic syntax highlighting for a language. However, if you want to deploy the application, it wraps a native dependency, which adds some complexity.

In contrast, JavaScript has a vast number of mature syntax highlighting libraries. To name a few, there are highlight.js, Prism.js (used on this blog), and shiki.js. Especially the first two are very mature, with multiple plugins and themes, and have simple APIs.

As a .NET developer, the obvious problem with JavaScript is that you need to learn and opt into a completely separate toolchain, working with Node.js and npm. That seems like a lot of overhead just to use a small feature.

So we are stuck in a dilemma. We either take the C# (+ Native) route, or we have to switch to JavaScript.

Or... we just call JavaScript directly from our .NET application 🤯

2. Running JavaScript in .NET

Once you decide to run JavaScript in your .NET code, you consider a few options. You could borrow a JavaScript engine and have it run your JavaScript for you, but you haven't really solved the problem – you still need to install Node.js.

Another option is to bundle a JavaScript engine directly in your library. This isn't as crazy as it sounds; there are several NuGet packages that take this approach and then expose a C# layer to interact with the engine.

Here is a list of some packages you can use.

Jering.JavaScript.NodeJS

This library takes the first approach mentioned above. It does not include Node.js in the package. Instead, it provides a C# API for executing JavaScript code and calls Node.js installed on your machine. This could be useful in environments where you know both are installed, but it doesn't really solve the problem I want to avoid.

ChakraCore

ChakraCore is the JavaScript engine originally used by Edge before it switched to a Chromium-based engine. According to the GitHub project description:

ChakraCore is a JavaScript engine with a C API that you can use to add JavaScript support to any C or C-compatible project. It can be compiled on Linux, macOS, and Windows for x64 processors. x86 and ARM are only supported on Windows.

So ChakraCore includes a native dependency, but since C# can P/Invoke into native libraries, that in itself is not a problem. However, it does introduce some deployment challenges.

ClearScript (V8)

Node.js, Chromium, Chrome, and the latest Edge all use the V8 JavaScript engine. The Microsoft.ClearScript package provides a wrapper for this library, offering a C# interface to call the V8 library. Like ChakraCore, the V8 engine itself is a native dependency. The ClearScript library handles the P/Invoke calls and provides a nice C# API, but you still have to ensure you deploy the correct native libraries for your target platform.

Jint

Jint is interesting because it is a JavaScript interpreter fully implemented in .NET, without any native dependencies! It fully supports ECMAScript 5.1 (ES5) and supports .NET Standard 2.0, so you can use it in all your projects!

Jurassic

Jurassic is another .NET implementation of a JavaScript engine, similar to Jint. Also like Jint, it supports all of ES5 and seems to partially support ES6. Unlike Jint, Jurassic is not an interpreter; it compiles JavaScript to IL, making it very fast, and it has no native dependencies.

So, among all these choices, which one should you pick?

3. JavaScriptEngineSwitcher: When One JS Engine Isn't Enough

There is also a great project that allows you to easily try any of the above libraries. While all libraries allow you to run JavaScript, they all have slightly different C# APIs to interact with. This can make comparing them a bit painful because you have to learn a different API for each library.

JavaScriptEngineSwitcher is a library that provides wrappers for all the libraries I mentioned and more:

Each library is in a separate package (engines with native dependencies require an additional native package), and there is a Core package that provides the common API. Even if you don't plan to switch JS engines, I tend to use the JavaScriptEngineSwitcher wrapper library whenever possible, so you don't have to figure out a new API if you need to switch engines later.

Changing the JavaScript engine used in a .NET project is, in my opinion, entirely feasible. For example, I started with Jint, but when I needed to execute larger scripts, I ran into performance issues and switched to Jurassic. JavaScriptEngineSwitcher made this trivial – just add a new package to my project and change some initialization code.

I only recently discovered the JavaScriptEngineSwitcher library, but with nearly a million downloads for the latest version, it is used in the .NET static site builder Statiq. In the final part of this article, I'll show a basic usage example.

4. Example: Running prism.js in a Console Application with JavaScriptEngineSwitcher

At the beginning of this article, I discussed a specific scenario – syntax highlighting of code blocks. In this section, I'll show how to use prism.js to highlight a small piece of code and run it in a console application.

Start by adding a reference to the JavaScriptEngineSwitcher.Jurassic NuGet package.

dotnet add package JavaScriptEngineSwitcher.Jurassic

Next, download the JavaScript file you want to run. For example, I downloaded the prism.js file from the Prism.js website and added C# to the default set of supported languages. After placing the file in the root of the project folder, I updated the file to be an embedded resource. You can do this in your IDE or manually edit the project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="JavaScriptEngineSwitcher.Jurassic" Version="3.17.4" />
  </ItemGroup>

  <!-- 👇 Make prism.js an embedded resource -->
  <ItemGroup>
    <None Remove="prism.js" />
    <EmbeddedResource Include="prism.js" />
  </ItemGroup>

</Project>

The rest is to write the code to run the script in our program. The following code snippet sets up the JavaScript engine, loads the embedded prism.js library from the assembly, and executes it.

using JavaScriptEngineSwitcher.Jurassic;

// Create an instance of the JavaScript engine
IJsEngine engine = new JurassicJsEngine();

// Execute the embedded resource called JsInDotnet.prism.js from the provided assembly
engine.ExecuteResource("JsInDotnet.prism.js", typeof(Program).Assembly);

Now we can run our own JavaScript commands in the same context. We can pass values from C# to the JavaScript engine using SetVariableName, Execute, and Evaluate:

// This is the code we want to highlight
string code = @"
using System;

public class Test : ITest
{
    public int ID { get; set; }
    public string Name { get; set; }
}";

// set the JavaScript variable called "input" to the value of the C# variable "code"
engine.SetVariableValue("input", code);

// set the JavaScript variable called "lang" to the string "csharp"
engine.SetVariableValue("lang", "csharp");

// run the Prism.highlight() function, and set the result to the "highlighted" variable
engine.Execute($"highlighted = Prism.highlight(input, Prism.languages.csharp, lang)");

// extract the value of "highlighted" from JavaScript to C#
string result = engine.Evaluate<string>("highlighted");

Console.WriteLine(result);

When you run them together, the highlighted code is printed to the console:

<span class="token keyword">using</span>
<span class="token namespace">System</span
><span class="token punctuation">;</span>

<span class="token keyword">public</span>
<span class="token keyword">class</span>
<span class="token class-name">Test</span>
<span class="token punctuation">:</span>
<span class="token type-list"><span class="token class-name">ITest</span></span>
<span class="token punctuation">{</span>
<span class="token keyword">public</span>
<span class="token return-type class-name"
  ><span class="token keyword">int</span></span
>
ID <span class="token punctuation">{</span>
<span class="token keyword">get</span><span class="token punctuation">;</span>
<span class="token keyword">set</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span>
<span class="token return-type class-name"
  ><span class="token keyword">string</span></span
>
Name <span class="token punctuation">{</span>
<span class="token keyword">get</span><span class="token punctuation">;</span>
<span class="token keyword">set</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>

When rendered, it looks like this:

I was surprised by how simple the whole process was. Starting a JavaScript engine, loading the prism.js file, and executing our custom code was seamless. This was the perfect solution to the problem I was facing.

I clearly don't recommend this for all applications. If you need to run a lot of JavaScript, it's probably easier to just use the Node.js ecosystem and tools directly. But if you just want to leverage a small, standalone tool (like prism.js), then this is a good choice.

5. Summary

In this article, I showed how to use the JavaScriptEngineSwitcher NuGet package to run JavaScript in a .NET application. This package provides a consistent interface for many different JavaScript engines. Some of these engines (like Chakra Core and V8) rely on a native component, while others (like Jint and Jurassic) use only managed code. Finally, I demonstrated how you can use JavaScriptEngineSwitcher to run the Prism.js code highlighting library inside a .NET application.

Original article: https://andrewlock.net/running-javascript-in-a-dotnet-app-with-javascriptengineswitcher/

Author: Andrew Lock

Translation: Fine Coder

Keep Exploring

Related Reading

More Articles