Returning or Passing Managed Types to JavaScript
Microsoft Silverlight will reach end of support after October 2021. Learn more.
The HTML Bridge enables you to use managed types as parameters in JavaScript function invocations. In addition, Javascript functions can return managed types.
The following sections explain how this functionality works:
Primitive Types, Well-Known Types, and Structures
Dictionaries and Custom Types
Note: |
---|
Silverlight for Windows Phone does not support the HTML Bridge feature. |
Primitive Types, Well-Known Types, and Structures
This section shows how specific types are marshaled between managed code and Javascript.
Primitive Types
Strings are returned by value as JavaScript strings.
Null references and Nullable<T> are returned as the JavaScript null value.
Booleans are returned as JavaScript Boolean values.
A DateTime is returned by value as an instance of the JavaScript Date object.
A Char is returned by value as a single-character JavaScript string.
A managed number type is converted and returned to JavaScript as a Double.
Note: Because of this behavior, loss of precision can occur during the conversion. Furthermore, if you try to pass the resulting JavaScript number back to managed code (a process referred to as round-tripping), you can encounter an overflow when you try to cast back to the original managed type. For example, a managed UInt64 is passed to JavaScript as a double and then returned to, or accessed from, managed code. If the managed code tries to cast the resulting double back into UInt64, it may fail. For example, 0xFFFFFFFFFFFFFFFF becomes the double value 18446744073709552000, which overflows if you try to convert it back to an UInt64.
Bytes (including signed and unsigned variations) are returned as JavaScript numbers.
Enumerations are returned by value as JavaScript numbers.
GUIDs are returned by value as JavaScript strings using the following pattern: "382c74c3-721d-4f34-80e5-57657b6cbc27".
Objects are returned based on the following rules:
ScriptObject and other built-in HTML Bridge types are passed by reference back to JavaScript. This does not mean that the public managed API of these types becomes available to JavaScript. Built-in HTML Bridge types internally reference Document Object Model (DOM) objects, and it is these object references that are passed back to JavaScript.
Function delegates are passed by reference.
Complex .NET Framework reference types are passed by reference to JavaScript.
Managed Structures
Managed structures (other than those listed previously in this topic) are marshaled by value to a JavaScript object. Each field or property on a structure that is marked with the DataMemberAttribute is represented as a named property on the resulting JavaScript object.
Managed Event Delegates
Managed event delegates can be passed to JavaScript parameters that expect method references. Managed event delegates can also be returned from managed properties that are typed with EventHandler (for example, public EventHandler MyXYZ_Property {get; set;}.
Managed properties that use the event keyword cannot be used to return event delegates as properties. This occurs because the event keyword does not result in an actual property that can be assigned by using the equality operator (=).
This enables managed developers to directly use ASP.NET AJAX object methods and properties to register callbacks without the need for any additional code on the page. It also provides an alternative way for JavaScript to call back into managed code, without requiring the scriptable attribute. Because managed methods wrapped as event delegates occur independently of registering scriptable managed objects, you do not have to attribute the managed methods that are used in the event delegates with the ScriptableMemberAttribute attribute.
You should define custom event delegates to match the JavaScript method signatures. When a JavaScript callback on a managed event delegate occurs, the HTML Bridge marshals the JavaScript parameters by position. The first JavaScript input parameter is mapped to the first parameter on the managed method, the second JavaScript input parameter is mapped to the second parameter on the managed method, and so on.
You can use the built-in ScriptEventHandler type to handle the common case of ASP.NET AJAX behavior and controls that raise events following the ASP.NET AJAX event pattern.
It is possible to round-trip a managed event delegate to JavaScript and then back into managed code by using ScriptObject. For example, managed code may pass a managed delegate to an ASP.NET AJAX type, which then exposes the method to a JavaScript property. Alternatively, a ScriptableMemberAttribute property typed as EventHandler can be used to return a managed event delegate to JavaScript. Managed code can then access the property by using the GetProperty method on the ScriptObject reference to the containing Silverlight control, as follows: HtmlPage.ControlElement.GetProperty("ScriptablePropertyTypedAsEventHandler").
If the calling managed code is inside the same Silverlight control instance that holds the original event delegate, the value from GetProperty(…).ManagedObject can be cast back to the delegate type.
If the calling managed code is in a different Silverlight control instance from where the event delegate came from, the return value from GetProperty is a ScriptObject reference, but the ManagedCode property always returns null. In this case, you can call InvokeSelf on the ScriptObject to invoke the delegate in a late-bound manner.
For ScriptableMemberAttribute properties typed as EventHandler, you can also call the Invoke method and pass the name of the EventHandler typed property as the method name. In other words, you can either invoke the property directly with Invoke, or you can call GetProperty and then InvokeSelf on it.
JavaScript code that has a reference to a managed event delegate can also invoke the function delegate by using the JavaScript apply function. Note that apply is the only property or method of the JavaScript Function object that is currently supported on managed event delegates.
You can also pass the following HTML Bridge types to JavaScript:
HtmlElement can be passed to JavaScript code that expects a reference to an HTML element.
ScriptObjectCollection can be passed to JavaScript code that expects a reference to a collection of HTML elements.
HtmlDocument can be passed to JavaScript code that expects a reference to an HTML document.
HtmlObject can be passed to JavaScript code that expects a reference to an HTML object (which includes HTML elements, an HTML document, and other HTML types that are neither elements nor documents).
ScriptObject can be passed to JavaScript code that expects a reference to a JavaScript object. This provides the lowest common denominator approach for passing references to arbitrary JavaScript objects from managed code back to JavaScript array and list types.
The default is by-reference marshaling from managed code to JavaScript.
By-Reference Marshaling
By default, properties and return values typed as IList (which includes both arrays and generic list types) are returned by reference, using a JavaScript wrapper that is similar to an array. Therefore, you do not have to explicitly attribute IList types with [ScriptableTypeAttribute]. However, complex .NET Framework types contained in the array or list must be correctly attributed as [ScriptableMemberAttribute].
Only single-dimensional arrays are supported. Multidimensional arrays (for example, string[4,5]) cannot be returned from scriptable properties or methods. However, you can return jagged arrays (which are also called arrays of arrays).
For array methods that allow setting values, see Passing JavaScript Objects to Managed Code for information about how you can create complex .NET Framework types so that they can be set as array or list elements.
The array-like JavaScript wrapper supports a limited subset of the methods usually found on a JavaScript Array type. For methods that enable setting items, an exception is returned to the JavaScript caller if the wrong type is used.
It is theoretically possible for a by-reference array to be populated with both by-reference and by-value instances. However, this is explicitly blocked by the marshaling layer. If JavaScript code tries to insert or set an object in a by-reference array that is not of the appropriate type (for example, a by-reference script wrapper for complex types), an exception is returned to the calling JavaScript code. Script code must always obtain a reference to a by-reference script wrapper in order to insert or overwrite a value in a by-reference array wrapper.
The JavaScript array wrapper supports the following operations:
length – Returns the count of the members in the array.
[]item syntax – Enables JavaScript developers to use the familiar myvar[4] syntax to get and set items in the array.
toArray() – Returns a managed array wrapper representation if the underlying managed type is a list (basically, this is just a JavaScript helper that calls List.ToArray). If the underlying type is an array, no action is taken, and the method returns null.
pop – Returns the last entry in the list and removes the item from the underlying list. This method causes a JavaScript error if it is used against a .NET Framework array type, because .NET Framework arrays are fixed in size.
push(value1, value2, …) – Inserts one or more JavaScript types at the end of the managed list. See Passing JavaScript Objects to Managed Code for information about how JavaScript developers can pass complex type instances back to managed code. This method causes a JavaScript error if it is used against a .NET Framework array type, because .NET Framework arrays are fixed in size.
reverse – Reverses the order of the elements in the underlying array or list.
indexOf(searchString,[startIndex]) – Finds the first occurrence of searchString in the array. An optional zero-based starting index can be used to specify where to start the search in the array. Similar to JavaScript, indexOf can use objects for the search string parameter.
lastIndexOf(searchString, [startIndex]) - Finds the last occurrence of searchString in the array. An optional zero-based starting index can be used to specify where to start the backward search in the array. This enables you to start at startIndex and search backward for searchString.
shift – Returns the first element in list and removes it from the underlying list. This method causes a JavaScript error if it is used against a .NET Framework array type, because .NET Framework arrays are fixed in size.
unshift(value1, value2, …) - Inserts one or more JavaScript types at the beginning of the managed list. See Passing JavaScript Objects to Managed Code for information about how JavaScript developers can pass complex type instances back to managed code. This method causes a JavaScript error if it is used against a .NET Framework array type, because .NET Framework arrays are fixed in size.
splice(startIndex, [count], [newvalue1], [newvalue2], …) – Removes one or more items from the underlying list, starting at startIndex. If count is specified, count items starting from startIndex are removed. Otherwise, the list is truncated from startIndex onwards. You can optionally specify one or more new values to insert into the list at startIndex. This method causes a JavaScript error if it is used with a .NET Framework array type, because .NET Framework arrays are fixed in size.
By-Value Marshaling
By-value marshaling is not automatically supported. Instead you have two options:
You can serialize .NET Framework types into JavaScript Object Notation (JSON) strings, return or pass the JSON strings to JavaScript, and then JSON-deserialize the strings into a JavaScript object graph.
You can use JavaScript JSON serializers against the by-reference wrappers that are returned from managed code. For example, you could use the ASP.NET JSON serializer against one of the interop wrappers to disconnect from the managed instance.
You can explicitly specify that an IList typed property or method be returned by value with the SerializeToScriptByValue attribute.
Silverlight will recursively marshal all items in the managed array by value into a corresponding JavaScript array. Marshaling each managed type follows the rules described in Silverlight and JavaScript Marshaling as well as the JSON serialization rules supported by the underlying serializer.
When the JavaScript array is returned, there is no relationship between the data contained in the JavaScript array and the data contained in the original .NET Framework list or array type. Changes made in one will not be reflected in the other.
Dictionaries and Custom Types
The default is by-reference marshaling from managed code to JavaScript.
By-Reference Marshaling
By default, properties and return values typed as IDictionary are returned by reference, using a JavaScript wrapper that is similar to a dictionary. Because many IDictionary types exist in the .NET Framework, you do not have to explicitly attribute IDictionary types with Scriptable. Instead, Silverlight automatically marshals such types by reference to a dictionary-like JavaScript wrapper.
Because JavaScript objects are also JavaScript dictionaries, custom .NET Framework types will also be returned by reference using a dictionary-like JavaScript wrapper.
Note that IDictionary types must use only strings for keys. This means that a type such as Dictionary<string,MyCustomValueType> is supported, but a type such as Dictionary<MyCustomKeyType,MyCustomValueType> is not supported. An attempt to use an invalid type results in an ArgumentException when the property, method, or event is invoked.
When .NET Framework types other than IDictionary types are marshaled by reference, any scriptable properties, methods, or events on those types will also be available to JavaScript callers.
For dictionary methods that allow setting values, see Passing JavaScript Objects to Managed Code for information about how to create complex .NET Framework types so that they can be set as dictionary elements or properties on custom .NET Framework types.
It is theoretically possible for a dictionary wrapper to be populated with both by-reference and by-value instances. However, this is explicitly blocked by the marshaling layer. If JavaScript code tries to insert or set an item in a by-reference dictionary that is not of the appropriate underlying .NET Framework type, an exception is returned to the calling JavaScript code. Script code must always obtain a reference to a by-reference script wrapper in order to insert or overwrite a value in a by-reference dictionary wrapper.
The JavaScript dictionary wrapper supports the following operations:
[keyName] – JavaScript developers can set and get values in an IDictionary by using this syntax. They can also access scriptable property values on custom .NET Framework types by using the scriptable property names as the value of keyName. See Passing JavaScript Objects to Managed Code for information about how to create complex .NET Framework types and then set them as dictionary elements or properties of a custom type. Note that even if the underlying managed type is not a dictionary, this syntax is still supported.
.Property syntax – JavaScript developers can also access values in a dictionary by using the dot syntax. This is an alternative to the [keyname] syntax.
Adding new key-value pairs - JavaScript lets you arbitrarily add new key/value pairs to a JavaScript object. The dictionary wrapper supports this operation if the underlying type is an IDictionary. However, if this is attempted against a custom .NET Framework type, an error is returned to the JavaScript caller.
By-Value Marshaling
By-value marshaling is not automatically supported. Instead, you have two options:
You can serialize .NET Framework types into JSON strings, return or pass the JSON strings to JavaScript, and then JSON-deserialize the strings into a JavaScript object graph.
You can use the JavaScript support methods (see Constructing Managed Types from JavaScript) to perform a JSON-based conversion from a .NET Framework object graph to a JavaScript object graph.