1

I'm having some trouble passing JavaScript arrays and dictionary-type objects to methods in my managed code via the HTMLBridge. The infomation in Microsoft article detailing this topic and around the net have led me nowhere.

Using the following info from the linked article:

NET Framework properties or input parameters typed as object are converted using the following heuristics when marshaling by value to a target .NET Framework property or input parameter:

JavaScript arrays are converted to object[].

JavaScript dictionaries are converted to Dictionary<string,object>.

... I've attempted to pass arrays and dictionary-objects to my managed code in several ways, with no success:

Javascript:

var array = [{key: 1}, {key: 2}, {key: 3}];
silverlight_domElement.content.testObject.testMethod(array);

C# (attempt #1):

[ScriptableMember]

//THROWS CONVERSION EXCEPTION HERE
public void testMethod(Dictionary<string,object>[] arrayParam) 
{
    //...
}

C# (attempt #2):

[ScriptableMember]
public void testMethod(object arrayParam) 
{
    //THROWS CONVERSION EXCEPTION HERE
    Dictionary<string, object>[] arr = (Dictionary<string, object>[])arrayParam; 
}

C# (attempt #3):

[ScriptableMember]
public void testMethod(ScriptObject arrayParam)
{
    //THROWS CONVERSION EXCEPTION HERE
    Dictionary<string, object>[] arr = 
      arrayParam.ConvertTo<Dictionary<string, object>[]>();

}

Exceptions are of the form (where "TARGET TYPE" is the expected type of the object resulting from an explicit or implicit cast (including Object[]):

SCRIPT16389: System.ArgumentException: This object cannot be converted to the specified type TARGET TYPE. Parameter name: targetType

at System.Windows.Browser.ScriptObject.ConvertTo(Type targetType, Boolean allowSerialization)

at System.Windows.Hosting.ScriptingInterface.GetScriptParamValueForType(ScriptParam scriptParam, Type desiredType)

at System.Windows.Hosting.ScriptingInterface.ConvertFromScriptParams(ParameterInfo[] parameters, ScriptParam[] args)

at System.Windows.Browser.ManagedObjectInfo.ScriptMethod.Invoke(ManagedObject obj, InvokeType invokeType, ScriptParam[] args)

at System.Windows.Browser.ManagedObjectInfo.Invoke(ManagedObject obj, InvokeType invokeType, String memberName, ScriptParam[] args)

at System.Windows.Hosting.ManagedHost.InvokeScriptableMember(IntPtr pHandle, Int32 nMemberID, Int32 nInvokeType, Int32 nArgCount, ScriptParam[] pArgs, ScriptParam& pResult, ExceptionInfo& pExcepInfo)

(Analogous attempts were made to pass dictionary-objects to C# as Dictionary<string, object>).

Are these attempts the result of misinterpretations of the info in the aforementioned article and beyond? Or is my implementation simply incorrect?

Addendum:

I'm aware of a way to accomplish what I desire with ScriptObject.getProperty(), but I would like to deal with concrete, exact types if possible. Not to mention the fact it returns either a native type, String, or ScriptObject if the keyed value cannot be unboxed as either of the former two. I'd hate to be reduced to repeatedly calling it on an arbitrarily nested object until I arrive at a native type.

Kevin
  • 2,617
  • 29
  • 35
  • @RBarryYoung, I believe the term refers to a simple Javascript object consisting of key-value pairs, which is what I could infer from the paper. What else could it possibly refer to? – Kevin Feb 22 '13 at 19:17
  • I see it now. MS explains that in the preceding article. – RBarryYoung Feb 22 '13 at 19:31
  • Have you been able to get it to work for non-Array Javascript dictionaries? – RBarryYoung Feb 22 '13 at 19:38
  • Unfortunately no. As mentioned in the post: "(Analogous attempts were made to pass dictionary-objects to C# as Dictionary)" – Kevin Feb 22 '13 at 19:40

2 Answers2

1

it seems to be that

[{key: 1}, {key: 2}, {key: 3}]

is actually of type

object []

instead of a dictionary type.

You code is trying to cast an array into a dictionary, which will never work.

To clarify, because your top level object is an Array and thus object [], the JSON parser is not smart enough to know the individual types of each element in the array.

I'm afraid you're going to have to cast it to

object[]

and then process it like

Dictionary<string,object> cur;
foreach(object o in objArray)
{
   cur = (Dictionary<string,object>) o;

}
KDV
  • 730
  • 1
  • 6
  • 12
  • That's what I think is incorrect. You're code is trying to cast into Dictionary[] when the object script object is NOT a dictionary. It is an array. Thus it should be casted to object[]. – KDV Feb 22 '13 at 19:02
  • I'm afraid you're overlooking the brackets at the end of Dictionary[], which defines it as an array of Dictionary. Also, if you look at my code above, casting it from Object (which is a parent type of []) does not work. – Kevin Feb 22 '13 at 19:10
  • Kevin: but those are not dictionaries nor an array of dictionaries, they are just an array of Javascript objects. – RBarryYoung Feb 22 '13 at 19:15
  • @RBarryYoung: Yea, I believe we're all in agreement that they're Javascript Objects. What I am trying to say is that the linked article makes use of the term to differentiate simple key-value pair objects from other objects (such as Arrays or user-defined types). Do you disagree? – Kevin Feb 22 '13 at 19:22
  • @RBarryYoung: Every non-primitive in Javascript is an Object, therefore some terminology must be used to distinguish Arrays(Objects), from Date(Objects), from primitive(Objects) such as those in my code snippet. – Kevin Feb 22 '13 at 19:26
  • @Kevin you are correct, I found where Microsoft explains that. – RBarryYoung Feb 22 '13 at 19:36
  • @KDV: Your solution is noble, but fails (which is to be expected since Javascript dictionary objects fail to be converted to their expected type Dictionary). I've updated the question with a generalized version of the stack trace produced by your solution. – Kevin Feb 22 '13 at 20:00
1

After another look at the linked article, I believe the quoted text refers to marshalling operations on a managed object that take place after it has been created by createObject or createManagedObject.

So a class like this:

[ScriptableType]
public class TestClass
{
    public object arrayObj;
    public object dicObj;
    public TestClass sibling;

    public TestClass(){}
    public void testMethod(TestClass testClassObj)
    {
                               //Assuming argument came from Javascript
                               //& arrayObj and dicObj were assigned an 
                               //Array and dictionary-obj respectively

         testClassObj.arrayObj; //Formal type: Object. Actual type: object[]
         testClassObj.dicObj;   //Formal type: Object. Actual type: 
                                //                     Dictionary<string, object>
    }
}

Utilized by the Silverlight application like this:

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new MainPage();
    HtmlPage.RegisterCreatableType("TestClass", typeof(TestClass));
    HtmlPage.RegisterScriptableObject("scripTestClassObj", new TestClass());
}

Can be used like this on the client-side:

var testClassObj = silverlight_domElement.content
                    .services.CreateObject("TestClass");
testClassObj.arrayObj = [1,2,3];  
testClassObj.dicObj = {key: 1};  

silverlight_domElement.content.scripTestClassObj.testMethod(testClassObj);

//"sibling"'s arrayObj and dicObj types will have actual types
//of object[] and Dictionary<string, object> respectively
silverlight_domElement.content.scripTestClassObj.sibling = testClassObj;

In other words, it has no bearing on the functionality in discussion.

In the context of my question, it means that passing non-primitive Javascript values to managed code requires adherence to one of the following guidelines:

  1. The type of the target property or method parameter must be ScriptObject (or Object, since that is the class it derives from).
  2. The type of the target property or method parameter must either be explicitly or implicitly registered with HtmlPage.RegisterCreatableType, and the value passed must be the object resulting from a createObject or createManagedObject call with the registered alias (createObject) or type (createManagedObject).

Please feel free to correct me if any of the statements I've made are incorrect.

Kevin
  • 2,617
  • 29
  • 35