As was going through my JSON Serialization code the other day I kept re-thinking this idea I had some time ago: Why not use Microsoft’s built in JavaScript engine to perform the JavaScript and JSON deserialization. After all there is an official Microsoft JavaScript .NET language and it should be possible to take advantage of this from within an application. I finally had some time to check this out...
I started searching for how to do this and there’s not a heck of a lot of information available on this so I started playing around with this on my own. The good news is that it’s pretty straight forward to evaluate JavaScript code, the bad news is that for what I really need it for it doesn’t work quite right.
In order to use JScript you need to add a reference to the the Microsoft.vsa assembly.
<
add assembly="Microsoft.Vsa, Version=8.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/></assemblies>
First here are a few eval scenarios and how you can call them from a bit of C# code in ASP.NET:
using
Microsoft.JScript;
public partial class JavaScriptEval : System.Web.UI.Page
{
public static Microsoft.JScript.Vsa.VsaEngine Engine = Microsoft.JScript.Vsa.VsaEngine.CreateEngine();
public static object EvalJScript(string JScript)
{
object Result = null;
try
{
Result = Microsoft.JScript.Eval.JScriptEvaluate(JScript, Engine);
}
catch (Exception ex)
{
return ex.Message;
}
return Result;
}
public void Page_Load(object sender,EventArgs e)
{
// *** String Value
object Result = EvalJScript(@"('hello world: ' + new Date())");
Response.Write( Result.ToString() );
Response.Write(" Type: " + Result.GetType().Name);
Response.Write("<hr>");
// *** Return an integer or numeric - no conversion required
Result = EvalJScript(@"( 11 * 12)");
Response.Write(Result.ToString());
Response.Write(" Type: " + Result.GetType().Name);
Response.Write("<hr>");
// *** Date value - must be converted!
Result = EvalJScript(@"(new Date())");
Response.Write(Result);
Response.Write(" - Type: " + Result.GetType().Name + "<br>");
// *** Must convert from DateObject to DateTime by coercing
DateObject dateObject = Result as DateObject;
Result = Microsoft.JScript.Convert.Coerce(Result,typeof(DateTime));
Response.Write("Converted to DateTime: " + Result);
Response.Write("<hr>");
// *** Block of code (last assignment is the return value)
Result = EvalJScript(@"var out = 'hello'; for ( var x = 1; x < 10; x++) { out = out + 'Line ' + x + '<br>'; }");
Response.Write(Result);
Response.Write(" - Type: " + Result.GetType().Name);
Response.Write("<hr>");
/// *** Closure - calling a JavaScript Function with return value
Result = EvalJScript(@"( function Test(inputParm) { return 'hello world ' + inputParm; } )");
/// *** Invoke the function and retrieve the result
Closure close = Result as Closure;
// *** This requires full trust
Result = close.Invoke(close, new object[1] { "Start with this bub..." });
Response.Write(Result);
Response.Write(" - Type: " + Result.GetType().Name);
Response.Write("<hr>");
// *** JSON style object
Result = EvalJScript(@"( {""timeString"":'Time is: ' + new Date(),""dateValue"":new Date()} )");
JSObject obj = Result as JSObject;
Response.Write(Result);
// *** JavaScript style property array access can be used
object val = obj["dateValue"];
Response.Write(Microsoft.JScript.Convert.Coerce(val,typeof(DateTime)));
val = obj["timeString"];
Response.Write(val);
}
}
As you can see execution of simple bits of code is pretty straight forward. If you need to evaluate an expression that returns a simple type like string, bool or numeric you can simply eval the expression and you get the right value and type back.
If you eval something that results in a date however things get more tricky already. You basically need to coerce the JavaScript date into a .NET date. It’s fairly easy to do this luckily – and it might be a decent solution to the lousy kind of code you have to write to deal with all the different date formats available in JSON (new Date() syntax allows for string values, numeric values all in various format). This is somewhat useful.
More interesting is the ability to execute a whole block of code. There are a few ways to do this. You can simply pass the code directly and execute it which works just fine. The problem with this is that you can’t directly return a value. Instead the last assignment ends up as your return value. This is similar to the behavior you see in event handlers like onclick in Web pages where you sometimes get unexpected results if you don’t explicitly return a value. This is probably the best choice even though it’s a bit unconventional.
Result = EvalJScript(@"( function Test(inputParm) { return 'hello world ' + inputParm; } )");
What happens here is that rather than returning you an evaluated result the parser returns you the closure – ie. essentially a function pointer. You can now use Reflection to execute the Result which is a little more involved:
Closure close = Result as Closure;
Result = close.Invoke(close, new object[1] { "Start with this bub..." });
This works fine, but it requires full Reflection rights in order to be able to access this anonymous function. The closure is treated like a private/internal/protected member by Reflection and so you need Full trust in ASP.NET to execute this code. Bummer – this would be the most natural way to execute code because you’d have the ability to pass in parameter and get a return value using standard .NET Reflection logic. But given the rights issues this is probably not an option. I can’t figure out any other way to pass a parameter into the parser.
Next, there’s the actual issue I was after: Being able to pass an arbitrary object (in JSON style format) in and get this object back in some way to that can be parsed in .NET.
Result = EvalJScript(@"( {""timeString"":'Time is: ' + new Date(),""dateValue"":new Date()} )");
This returns a JavaScript object which can then be parsed with typical JavaScript member arrays syntax:
Result = EvalJScript(@"( {""timeString"":'Time is: ' + new Date(),""dateValue"":new Date()} )");
JSObject obj = Result as JSObject;
foreach ( string FieldName in obj)
{
object FieldVal = obj[FieldName];
Response.Write( FieldVal.ToString() + " - " + FieldVal.GetType().ToString() + "<br>");
}
This works and you can potentially parse out the different types , but that still leaves the issue of how to get an actual object from the JavaScript object or more specifically how to ‘cast’ this object to another object.
I’m just thinking out loud here <s>:
Say you have a JSON method you call and you have a method which takes an object as a strongly typed object parameter:
Public bool SaveCustomer(CustomerEntity Customer)
…
I suppose one could use the Customer object as the ‘schema’ and walk through that object with Reflection and then pull values from the JavaScript with the code above and then assign them.
I took a closer look at what MS Ajax is doing – and well they’re manually parsing the javascript which is fine, but MS AJAX doesn’t work with a lot of types and doesn’t work of generic type value returned at all – any object structure must exist on the client before it can be passed back (that’s what that long Proxy block for a class definition does basically – it creates type definitions for any parameters and return values).
I have my own JSON parsing code and it works reasonably well against a wide variety of types generically using a mechanism similar to the above but by parsing string values. But the code is pretty messy and it can easily be broken by some funky JSON formatting. It would be much nicer to leave this sort of thing to a JavaScript parser.
OTOH, manually parsing is probably a safer solution. Using the JSCript parser as shown above actually executes code on the server. If you take a JSON input and do that there’s a lot of potential for security issues.
Actually it’s surprising that the Jscript Eval works at all under a limited trust environment.
Anyway it’s an interesting experiment but for my needs probably a dead end. But these samples might help someone trying to add JavaScript support to a .NET app out or at least get them started.
Other Posts you might also like