From C# To Javascript and Back Again, Via ClearScript

Note: This is the second of two articles on integrating C# and scripting languages. You can read the second part (for Ruby) here.

I recently created an engine (I know, I know) to create text-based Interactive Fiction games in C#. My requirement was simple: create a core engine with some core functionality (rooms, commands, and objects) in .NET, and allow end-users to write their games in Javascript. In the process, I picked up a little-known gem called ClearScript as the vehicle of interaction.

Let’s start with my requirements: I should be able to write core functionality classes in C#, instantiate and extend them in ClearScript, and return them back to C# for consumption by the game engine. I also need strong typing of objects (despite the dynamic nature of scripting languages), and the ability to create delegates or functions inside my scripting language (and return those to C#, too).

You can view the source code on GitHub. This post summarizes my implementation, and the challenges I ran though.

Why ClearScript?

ClearScript is far from the most popular Javascript library for C#. Why did I choose it? Because it had a simple syntax, and more importantly, it allowed me to return strongly-typed objects to C# (including Action instances) where Javascript.NET, Jurassic, could not.

JavascriptRunner Class

I created a core “JavascriptRunner” class which encapsulates all the Javascript-centric invocation. It contains a simple constructor, and a method public T Execute(string script, IDictionary parameters).

The first parameter (the script) is the easiest part. The second parameter contains any variables from C# that I want to expose to Javascript (this makes up my “API” which users write against).

One issue I ran into is that ClearScript doesn’t support the widely-supported console.log method. To get around this, I simply registered the Console class to it:

engine.AddHostType("Console", typeof(Console));

Here’s how that looks, as of writing:


public JavascriptRunner()
{
this.engine = new V8ScriptEngine();
var assembly = System.Reflection.Assembly.Load("MeltDown.Core");

foreach (var type in assembly.GetTypes())
{
    engine.AddHostType(type.Name, type);
}

// Used for anonymous functions
engine.AddHostType("Action", typeof(Action));
// JS doesn't support console.log (JS/V8/Chrome style)
engine.AddHostType("Console", typeof(Console));

}

public T Execute(string script, IDictionary<string, object> parameters)
{

foreach (var kvp in parameters)
{
    // TODO: these are engine-scope. It doesn't reset.
    engine.AddHostObject(kvp.Key, kvp.Value);
}

var toReturn = engine.Evaluate(script);

if (toReturn is T)
{
    return (T)toReturn;
}
else
{

throw new ArgumentException("Expected " + typeof(T).FullName + " but got " + toReturn.GetType().FullName);
}
}

Notice I can’t pass in parameters per script execution (unlike the Ruby version). That’s okay too; there’s room to improve.

Converting ClearScript Arrays to C# Lists

The second major hurdle is that ClearScript returns an Array instance instead of an enumeration if I use typical Javascript list syntax, like this: var x = ['a', 17, 2.0 / 3].

To get around this, I created a ScriptHelper class. This class contains two methods: IsArray and ToList. In both methods, I rely on V8-specific implementation, and since the object input is dynamic, I can just call Javascript methods and get back what I want. Here’s how that looks (Javascript only):


public static List ToList(dynamic source)
{
for (int i = 0; i < source.length; i++)
{
object next = source[i];
found.Add(GetAsType(next));
}

return found;

}

public static bool IsArray(dynamic source) {
return source.constructor.name == "Array";
}

This allows me to write “Javascript-like” code without worrying about types.

Returning Delegates/Functions

The last hurdle was related to functions; given a delegate or function type (like Action), how can I return an object which I can type to a delegate type?

The answer was two parts. First, I need to register the Action class with ClearScript, like so:

engine.AddHostType("Action", typeof(Action));

The second was to wrap any functions into an Action constructor:


new Action(function() {
this.Description = "A steaming, hand-burning potato";
})

Conclusion

Given my requirements, I achieved them, and have code that allows me to write very “Javascript-like” code. That’s great; I can leverage C# to take care of some of the leaky abstractions, and everything works.

I suggest you download and play with the full source code from GitHub. You can look at the ScriptRunner.Tests project to see working Javascript code coming back to C#.

About Ashiq Alibhai, PMP

Ashiq has been coding C# since 2005. A desktop, web, and RIA application developer, he's touched ASP.NET MVC, ActiveRecord, Silverlight, NUnit, and all kinds of exciting .NET technologies. He started C# City in order to accelerate his .NET learning.
This entry was posted in Libraries, Wndows Forms and tagged , . Bookmark the permalink.

5 Responses to From C# To Javascript and Back Again, Via ClearScript

  1. satyendra pandey says:

    Another blog with code example to make c# to javascript and vice versa call. Begins from scratch . http://pandeysk.wordpress.com/2014/05/23/clearscript-step-by-step/

  2. Petr says:

    Hi I have a problem, how to convert a .net Array to JS array. Do you have any solution?
    this snippet works, but only partially
    c#
    _engine.AddHostObject(“someArray”, new string[] {“one”, “two”, “three”});

    JS
    var first = someArray[0]; // works
    var length = someArray.length; // undefined
    var length2 = someArray.Length; // works

    but JS array has length property, not Length

  3. Ashiq Alibhai, PMP says:

    @Petr I haven’t worked with Clearscript since 2013, so I can only speculate. someArray.length (lower-case L) works with objects that are natively Javascript objects. someArray.Length (capital L) works because you’re actually getting a C# array which was converted into a JS object.

    In my experience, crossing language boundaries is usually where you see failings or problems between scripting languages and the host language. You can try opening an issue against the Clearscript repository and asking them to fix this.

    You can also try Jurassic; personally, I didn’t find it as easy to use and interop with.

  4. Petr says:

    @Ashiq Thank you for your reply. Yes I found out that ClearScript prefers minimum amout of automatic conversion and passes a HostObject as much as possible. So I have to do the conversion of .net Array to JS Array myself.

    So the next question would be: how can I convert .net Array to JS Array when I have no control of the executed script. I have only some contract, that the script contains object “A”, which I know, is an Array so I have to pass a JS Array in.

    So far I have created a wrapper – derived JsArray : List class where I expose a “length” property
    public int length {get {return this.Count; }}

    But still I would have to write the wrapper for all JS Array functions. There must be a better way.

  5. Petr says:

    I got a reply on clearscript codeplex site, so I would like to share it.
    https://clearscript.codeplex.com/discussions/662302

Leave a Reply

Your email address will not be published. Required fields are marked *