Chris Pietschmann

husband, father, hacker, entrepreneur, futurist, innovator, autodidact

NAVIGATION - SEARCH

Mixing some Dynamic-ness with IronJS in .NET 4

I recently started messing around with IronJS again, and I must say that this project is pure awesomeness! One of the first things I noticed is that when you call the “Execute” methods to run some JavaScript code it returns an “object.” Now, while this works just fine, I would prefer if it returned a “dynamic” object while running against .NET 4.0. There may be some reason that Fredrik Holmstrom (the author) decided to return an ‘object’, so I don’t mean to sound negative, but you have to admit that dynamics are really cool, especially when working with a dynamic language like JavaScript.

Basics of using IronJS

There are articles that cover the usage of IronJS, but here’s a very brief summary:

// Instantiate the IronJS Engine
var context = new IronJS.Hosting.CSharp.Context();

var result = context.Execute("2 + 2");
// returns 4

var result = context.Execute("'Ch' + 'ris'");
// returns "Chris"

Returning JavaScript Objects

The previous examples were extremely simple just to give you an idea if you are unfamiliar. Now I will show a more interesting example that returns a JavaScript object instead of just a simple, single value.

var result = context.Execute(@"
(function(){
  // do something here
  var id = 42;
  var name = 'chris';
  return { id: id, name: name }
})()
");

This returns a JavaScript object with “id” and “name” properties set respectively to 42 and “chris”.

Retrieving Object Values

Retrieving the JavaScript object values is pretty straight forward, although you must reach for them through the “CommonObject.Members” property.

var obj = (CommonObject)result;

var id = obj.Members["id"];
var name = obj.Members["name"];

Using Dynamics (via DynamicCommonObject class) to Retrieve Object Values

Wouldn’t it be nice to use Dynamics to get at the JavaScript object values? Yes, I agree that it would be.

Here’s an example of performing the previous example by using the DynamicCommonObject wrapper class (code listed below) to do it with .NET 4.0’s Dynamic object support.

var obj = new DynamicCommonObject(result as CommonObject);

// like properties on the object
var id = obj.id;
var name = obj.name;

// using the indexer property (not dynamic, but also simpler)
var id = obj["id"];
var name = obj["name"];

What if the property doesn’t exist? Well let me show you…

var firstname = obj.firstname;
// throws RuntimeBinderException with message:
// "'DynamicCommonObject' does not contain a definition for 'notexist'"

var firstname = obj["firstname"];
// returns IronJS.Undefined

If you want to change the behavior of DynamicCommonObject when the requested property name does not exist, then you can modify it’s code; it’s listed below.

DynamicCommonObject Wrapper Class Source Code

// Copyright (c) 2011 Chris Pietschmann - http://pietschsoft.com
// This work is licensed under a Creative Commons Attribution 3.0 Unites States License
// http://creativecommons.org/licenses/by/3.0/us/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using IronJS;

public class DynamicCommonObject : DynamicObject, IEnumerable, IEnumerable<KeyValuePair<string, object>>, IDictionary<string, object>
{
    public DynamicCommonObject(CommonObject commonObject)
    {
        this.CommonObject = commonObject;
    }

    public CommonObject CommonObject { get; private set; }

    #region "DynamicObject Overrides"

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var r = this.Keys.Contains(binder.Name) ? this[binder.Name] : null;
        result = getClrBoxedValue(r);

        if (result == null)
        {
            return false;
        }
        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        this.CommonObject.Put(binder.Name, value);
        return true;
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return this.CommonObject.Members.Keys;
    }

    #endregion

    #region "IEnumerable<KeyValuePair<string, object>> Implementation"

    public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
    {
        return this.CommonObject.Members.Select(d => new KeyValuePair<string, object>(d.Key, getClrBoxedValue(d.Value))).AsEnumerable().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    #endregion

    #region "IDictionary Implementation"

    public void Add(string key, object value)
    {
        this.CommonObject.Put(key, value);
    }

    public bool ContainsKey(string key)
    {
        return this.CommonObject.Members.Keys.Contains(key);
    }

    public ICollection<string> Keys
    {
        get { return this.CommonObject.Members.Keys; }
    }

    public bool Remove(string key)
    {
        return this.CommonObject.Members.Remove(key);
    }

    public bool TryGetValue(string key, out object value)
    {
        object v;
        var retVal = this.CommonObject.Members.TryGetValue(key, out v);

        value = getClrBoxedValue(v);
        return retVal;
    }

    public ICollection<object> Values
    {
        get {
            return this.CommonObject.Members.Values.Select(d => getClrBoxedValue(d)).ToArray();
        }
    }

    public object this[string key]
    {
        get
        {
            return getClrBoxedValue(this.CommonObject.Get(key));
        }
        set
        {
            this.CommonObject.Put(key, value);
        }
    }

    public void Add(KeyValuePair<string, object> item)
    {
        this.CommonObject.Members.Add(item.Key, item.Value);
    }

    public void Clear()
    {
        this.CommonObject.Members.Clear();
    }

    public bool Contains(KeyValuePair<string, object> item)
    {
        return this.CommonObject.Members.Contains(item);
    }

    public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public int Count
    {
        get { return this.CommonObject.Members.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<string, object> item)
    {
        var v = this.CommonObject.Members[item.Key];
        if (v == item.Value)
        {
            return this.CommonObject.Members.Remove(item.Key);
        }
        return false;
    }

    #endregion

    #region "Static Methods"
        
    private static object getClrBoxedValue(object obj)
    {
        if (obj is IronJS.BoxedValue)
        {
            return ((IronJS.BoxedValue)obj).ClrBoxed;
        }
        return obj;
    }

    #endregion
}

More Information on IronJS

If you aren’t familiar with IronJS, I encourage you to go check out one or more of the following links:

blog comments powered by Disqus