Thursday, June 30, 2011

Running JavaScript from C# with a hint of dynamic

The Noesis JavaScript.NET run-time is probably the fastest run-time that you can use from C# on Windows so far. The reason why, it uses Google V8 2.2 engine from 09/2010 (remark:this project seems to have been abandoned by its author :<) .

The Jurassic JavaScript run time is not as fast, but because written in C# provides better integration with the .NET world, if you really need it.

But both run-times do not let you access JavaScript objects and arrays in the C# world using the dynamic syntax available in JavaScript.

Here is a sample from the Noesis codeplex web site
JavascriptContext context = new JavascriptContext();

context.SetParameter("console", new SystemConsole());
context.SetParameter("message", "Hello World !");
context.SetParameter("number", 1);

string script = @"
    var i;
    for (i = 0; i < 5; i++)
        console.Print(message + ' (' + i + ')');
    number += i;
";

context.Run(script);

Console.WriteLine("number: " + context.GetParameter("number"));

Using the dynamic feature of C# 4.0 and my library DynamicJavaScriptRunTimes.net,  You can write the same code this way
dynamic jsContext = new DynamicJavascriptContext(
                          new JavascriptContext()
                    );                                               
jsContext.message = "Hello World !";
jsContext.number = 1;

string script = @"
    var i = 0;
    for (i = 0; i < 5; i++)
        console.log(message + ' (' + i + ')');
    number += i;
";

jsContext.Run(script);

Console.WriteLine("number: " + jsContext.number);


JavaScript array are translated into  .NET array and JavaScript object are translated into .NET Dictonary<string, object>. The method jsContext.Array() is a helper to create an array in a JavaScript like syntax, inspired by DynamicSugar.Net.

array:
How to create an array in C#, modify it in JavaScript and then read it back in C#.
dynamic jsContext   = new DynamicJavascriptContext(
                           new JavascriptContext()
                      );
jsContext.a = new object [] { 1, 2, 3 };  // Regular Syntax
jsContext.a = jsContext.Array( 1, 2, 3 ); // My Syntax

string script = @"
    a.push(4);
";
            
jsContext.Run(script);

Assert.AreEqual(4, jsContext.a.Length);

for(var i=0; i < jsContext.a.Length; i++)
    Assert.AreEqual(i+1, jsContext.a[i]);


Objects:
As you can see nested objects and arrays are supported. The [ ] syntax to access a property is also supported.
dynamic jsContext = new DynamicJavascriptContext(
                          new JavascriptContext()
                    );
string script = @"
    Configuration =  {
        Server   :    'TOTO',
        Database :    'Rene', 
        Debug    :    true, 
        MaxUser  :    3, 
        Users    :    [
            { UserName:'rdescartes'    ,FirstName:'rene'      ,LastName:'descartes'     }, 
            { UserName:'bpascal'       ,FirstName:'blaise'    ,LastName:'pascal'        }, 
            { UserName:'cmontesquieu'  ,FirstName:'charles'   ,LastName:'montesquieu'   } 
        ]
    }
";

jsContext.Run(script);

Assert.AreEqual("TOTO"      , jsContext.Configuration.Server);
Assert.AreEqual("Rene"      , jsContext.Configuration.Database);
Assert.AreEqual(true        , jsContext.Configuration.Debug);
Assert.AreEqual(3           , jsContext.Configuration.MaxUser);
Assert.AreEqual(3           , jsContext.Configuration.Users.Length);
Assert.AreEqual("rdescartes", jsContext.Configuration.Users[0].UserName);

Assert.AreEqual("TOTO"      , jsContext["Configuration"]["Server"]);
Assert.AreEqual("Rene"      , jsContext["Configuration"]["Database"]);
Assert.AreEqual(true        , jsContext["Configuration"]["Debug"]);
Assert.AreEqual(3           , jsContext["Configuration"]["MaxUser"]);
Assert.AreEqual(3           , jsContext["Configuration"]["Users"].Length);
Assert.AreEqual("rdescartes", jsContext["Configuration"]["Users"][0].UserName);


More Objects
We can also create objects from C#, pass them to the JavaScript run-time and then access the objects again. The method jsContext.Object() is a helper method inspired by DynamicSugar.Net to create object in a JavaScript like syntax or pass a POCO.

Note: Objects and arrays are not passed by reference. The data is copied from one world to the other.

dynamic jsContext   = new DynamicJavascriptContext(
                            new JavascriptContext()
                      );
                      
jsContext.i = jsContext.Object(
    new {
        a = jsContext.Object( new { LastName="Torres", Age=46 } ),
        b = jsContext.Object( new Person("Ferry", 47) ),
    }
);
string script = @"
    var p1 = {
        Name : i.a.LastName,
        Age  : i.a.Age
    }
    var p2 = {
        Name : i.b.LastName,
        Age  : i.b.Age
    }
";

jsContext.Run(script);

Assert.AreEqual("Torres", jsContext.p1.Name);
Assert.AreEqual("Torres", jsContext.p1["Name"]);
Assert.AreEqual("Torres", jsContext["p1"]["Name"]);

Assert.AreEqual(46, jsContext.p1.Age);
Assert.AreEqual(46, jsContext.p1["Age"]);
Assert.AreEqual(46, jsContext["p1"]["Age"]);

Assert.AreEqual("Ferry", jsContext.p2.Name);
Assert.AreEqual("Ferry", jsContext.p2["Name"]);
Assert.AreEqual("Ferry", jsContext["p2"]["Name"]);

Assert.AreEqual(47, jsContext.p2.Age);
Assert.AreEqual(47, jsContext.p2["Age"]);
Assert.AreEqual(47, jsContext["p2"]["Age"]);            

More Samples
Assert.AreEqual(
    "Fred",
    jsContext.Run(@"
        function Person(firstName){ this.FirstName = firstName; }
        (new Person('Fred'))"
    ).FirstName
);

////////////////////////////////////////////

DateTime refDate = new DateTime(1964, 12, 11, 01, 02, 03);

string script = @"
    var O2 = {
                F2: function(pInt,pDouble,pString,pBool,pDate) { 
                        return ''+this.Internal+'-'+pInt+'-'+pDouble+'-'+pString+'-'+pBool+'-'+formatDateUS(pDate);
                },
                Internal:1
                }
";

jsContext.Load("format", Assembly.GetExecutingAssembly());

jsContext.Run(script);

var expectedF2 = "1-1-123.456-hello-true-12/11/1964 1:2:3";
var f2Result   = jsContext.Call("O2.F2", 1, 123.456, "hello", true, refDate);

Assert.AreEqual(expectedF2, f2Result);



The DynamicJavascriptContext class
/// <summary>
/// Run the script and return the last value evaluated. Executing a declaration function
/// or a global object literal, will load the function or object in the JavaScript context.
/// </summary>
/// <param name="script"></param>
/// <returns>
/// </returns>
public object Run(string script);

/// <summary>
/// Load a JavaScript text file or text ressource in the JavaScript context
/// </summary>
/// <param name="name">The name without the .js extension</param>
/// <param name="assembly">The assembly is loading a ressource</param>
public void Load(string name, Assembly assembly = null);

/// <summary>
/// Execute a javascript global function or method
/// </summary>
/// <param name="functionName">the function name</param>
/// <param name="parameters">The parameters</param>
/// <returns></returns>
public object Call(string functionName, params object[] parameters);

/// <summary>
/// Helper function to make date compatible with the JavaScript
/// run time. Jurassic date are not .net datetime and need a convertion
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
public object Date(DateTime d){);

/// <summary>
/// Helper function to make array compatible with the JavaScript run time.
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public object array(params object [] array);

/// <summary>
/// Helper function to make a JavaScript object compatible with the JavaScript run-time
/// </summary>
/// <param name="netObject"></param>
/// <returns></returns>
public object Object(object netObject);



Conclusion

DynamicJavaScriptRunTimes.net

14 comments:

  1. Superb explanation & it's too clear to understand the concept as well, keep sharing admin with some updated information with right examples.Keep update more posts.basics
    school signs uk

    ReplyDelete
  2. This blog gives very important info about .Net Thanks for sharing
    Dot Net Online Training Bangalore

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. I like your blog. Your blog post provides a good starting point for exploring this approach.
    Java has endured over time

    ReplyDelete