JavaScript from C# and back again – ClearScript

An image of the impossible Penrose triangle illustrating a there-and-back journey.

As the ClearScript faqtorial says ‘ClearScript is a library that allows you to add scripting to your .NET applications’. So – if you need to provide a scripting facility in your C# app then ClearScript might be an option.

Here’s what I’m working on: The purpose of my project is to enable scripting for development of a new UI for an existing web application. The tech approach to the new UI uses for the front end a C#-based, in-house developed, generator that emits Semantic UI, and the back end a SQL Server DB.  The design relies very heavily on an object broker, and will use JavaScript as the procedural layer at the server. 

Therefore, the users – meaning the dev team – will be familiar with JavaScript but not C#.  Wrapping the C# features that the app needs means that we can present them to the JS developers as ‘just another JavaScript library’, which is a widely accepted paradigm for JavaScript folks.

The concept is to make the JS as simple, repetitive, and robust as possible whilst using the power of C# to handle as much as possible of everything else.

Our main constraint is that we have to use MS tools, so the server is Windows, with IIS and C#.

C# takes care of:

  • Business object creation
  • Calling SQL functions (read, save, delete, search)
  • Getting / setting UI request & response values
  • Communicating with the client

JS covers 

  • Requesting instantiation of business objects
  • Setting object props
  • Requesting DB activity which is invoked as methods of business objects
  • Handling errors
  • Setting up response data

Based on the requirement above, we need some means of executing JS from C# – enter ClearScript.

Should I use it ?

I always urge cuation before coding to rely on any third party library or code. Looking at the ClearScript GitHub repository today, it’s had lots of recent activity up to 16 days ago, and 171 commits overall. StackOverflow has a topic specially for ClearScript and while its not the busiest, there are reasonable questions and viable answers. The oldest mention I can find in Google is 2014 and there are various how-to and other blog entries over the years. So, this is a 6-year old project that continues to be maintained. In my opinion its a live project, not a fad and not an internship project.

Lets go ahead and set up a super-simple ClearScript example. Open Visual Studio and start a new console project. Now go to Project > Manage NuGet packages, click browse and enter ‘clearscript’ in the search box. The correct package is Microsoft.Clearscript.V8 – install it.

Make your code look like this and hit F5.

using System;
using Microsoft.ClearScript.V8;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            V8ScriptEngine engine = new V8ScriptEngine();
            engine.AddHostType(typeof(Console));
            Console.WriteLine("Hello from C#");
            engine.Execute(@"
			Console.WriteLine('Hello from Javascript');
		");
            Console.ReadKey();

        }
    }
}

The console will appear showing this:

What just happened is this – C# compiled and started the code. It wrote to the console the ‘Hello from C#’ – there’s nothing special so far. Then it instantiated the ClearScript engine and passed it a reference to the Console via the AddHostType line. Next it executed the JavaScript which produced the ‘Hello from Javascript’ output. Yes folks, that last line was running inside a little JavaScript processor bubble.

We’ve seen an example of exposing a host type for the Console. We can also pass in to the ClearScript engine a ready-made object via:

engine.AddHostObject("xml", new XmlHandler());

This requires two parameters – the first is the object name that JavaScript can use to reference the object being published, and the second is the object instance. The difference here should be clear – AddHostType() can be used within the JavaScript to create new objects from the class type, but with AddHostObject you can only talk to the object past into the method.

What’s next / what can I do with it ?

Before we head off into the sunset, we need to think about security. Via the AddHostType command, we can (but should not !) effectively reveal the full power of the C# executable environment to JavaScript. Think about that for a moment – that means System for example, which bestows the power to read, write, erase, change and otherwise mess with local files. Imagine enabling that for a bad actor via JavaScript ! 

There’s a reason why browsers execute JavaScript in a controlled environment !

Clearly then, we don’t want to give the ClearScript engine access to anything beyond the minimum features of C# that we need to provide to achieve the requirements for which we are enabling JavaScript in the first place.

The optimum stance to take is to never expose the C# architecture to JavaScript, but instead create custom classes that wrap all those goodies in code that ‘you’ control. This is good because: 

  • there is little chance of ‘leaking’ access to something by mistake;
  • calls from JS to the wrapped features must pass through your code where you can sanitize and mitigate the content of the call parameters and head off anything malicious. This is the same as sanitizing inputs from web forms against JS or SQL injection.;
  • Not immediately obvious, but when you have cause to switch away from to ClearScript in the future, your wrapped methods will require minimal alteration.

What about scaling it out ?

As as someone who gets annoyed at the glut of to-do or pizza ordering demos and the derth of what comes next, lets look at issues of scaling this into the real world.

In my example, I will have a large number of JavaScript script files acting as ‘receivers’ of end user page requests. Given an app page with various buttons, links and menus, the app user can invoke any number of actions and routes. The job of the receiver is to catch the requested action, validate it and carry it out. On the basis that the current app UI has many pages, we will have many receivers, meaning many JS files as start points. We will also have various ‘standard’ functions – perhaps validations, injection-sanitizers, data format checkers, etc, which will be carved out into ‘standardised’ modules and called upon by the ‘receiver’ JS files as they are needed.

Conclusion: Keeping our code modular requires a module pattern (think include-files). Personally, I like ES6 modules because an ES6 module is a file containing JS code that is automatically strict-mode code, and gives file-scope to vars inside the file without any additional overhead. There’s a good writeup here.

Back at out C# code, the ClearScript JS V8 Engine has a bunch of configurations that can be applied, with one of these being via the engine.DocumentSettings.AccessFlags property which sets the ‘loading’ behaviour of the engine. The enumeration of option values gives us options for no loading, loading from a local file location, loading from the web, or a mix of local and web. From my perspective I need local loading only, so lets see if we can make that work.

We will also need to set the documentSettings.SearchPath property to let the engine know where to look for scripts. Get this wrong then you see:

Exception thrown: 'Microsoft.ClearScript.ScriptEngineException' in ClearScript.V8.dll An unhandled exception of type 'Microsoft.ClearScript.ScriptEngineException' occurred in ClearScript.V8.dll Error: Could not load file or assembly 'a.js' or one of its dependencies. The system cannot find the file specified.

The code below illustrates how to configure for ES6 imports and a simple illustration of how to code the JS files export functions follows.

using System;
using Microsoft.ClearScript;
using Microsoft.ClearScript.JavaScript;
using Microsoft.ClearScript.V8;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            V8ScriptEngine engine = new V8ScriptEngine();
            engine.DocumentSettings.SearchPath = @"c:\temp\js\";
            engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
            engine.AddHostType(typeof(Console));
            Console.WriteLine("Hello from C#");
            engine.Execute(new DocumentInfo() { Category = ModuleCategory.Standard }, @"
			    Console.WriteLine('Hello from Javascript');
                import {print} from 'a.js';
                import {add} from 'b.js';
                print(add(30, 12));
			    Console.WriteLine('Javascript signing off...');
		    ");
            Console.ReadKey();

        }
    }
}

We add the using references to Microsoft.ClearScript at line 2 to power the AccessFlags property at line 14 and the DocumentInfo setting at line 17. DocumentAccessFlags gives us the power to specify loading from the file system (EnableFileLoading), or web loading, etc.

DocumentInfo allows us to specify the module category behaviour of the engine – in this case we used the Standard (read ES6) module loading approach. The other option is ‘CommonJS‘ which sets the module loading approach to the CommonJS version. Note we need the using reference to Microsoft.ClearScript.JavaScript; for the ModuleCategory enumeration.

The JS files contain

// File a.js
export function print(txt) {
    Console.WriteLine('The answer to Life etc is ... ' + txt);
  }

// File b.js 
export function add(var1, var2) {
    return var1 + var2;
  } 

And the result of all this is

Of course the key point here is that we used ES6 module loading to pull in the functions for Add() and print(), with the bonus that the print() function uses the Console.WriteLine() static function passed through the interface from C#.

Conclusion: We can extend this approach to work for a mid to large-scale application consisting of hundreds of individual page receiver scripts that import standard functions. The fact that engine.DocumentSettings.SearchPath can be a semicolon-delimited list provides further scope to subdivide the modules for file-management optimisation.

Useful Microsoft ClearScript Resources:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: