Constructing Managed Types from JavaScript

Microsoft Silverlight will reach end of support after October 2021. Learn more.

Because JavaScript developers can manipulate managed arrays, lists, and dictionaries by reference, there needs to be a mechanism by which JavaScript code can construct instances of managed types and convert from JavaScript object graphs to the corresponding managed instances. These instances can then be used to add items to JavaScript arrays and dictionaries that are wrappers around their managed counterparts.

You can obtain wrappers around managed types in two ways:

  • By explicitly registering types by using the RegisterCreateableType method.

  • By using helper methods that are available from registered script endpoints.

NoteNote:

Silverlight for Windows Phone Silverlight for Windows Phone does not support the HTML Bridge feature.

Explicit Support for Creating Script Wrappers

When you register a type by using the RegisterCreateableType method, JavaScript code can obtain a wrapper around the registered type by using one of the following methods:

Content.services.createObject(scriptAlias);

-or-

Content.services.createObject(scriptAlias, jsObject);

where scriptAlias is a type alias that is registered through the RegisterCreateableType method.

These two methods are exposed directly from the registered endpoint for a scriptable object.

createObject(string scriptAlias)

Return type: jsObject

Parameters: stringscriptAlias

Description: Given a registered scriptAlias, this method returns a script wrapper for the corresponding managed type.

For reference types, the returned object is a JavaScript object that acts as a wrapper for the managed type. If the managed type is not scriptable (that is, it has no scriptable properties, methods, or events), the returned JavaScript object is a black box (that is, it can be passed back to managed code), but there are no scriptable endpoints exposed to JavaScript callers.

For value types, JavaScript receives either a JavaScript primitive value or a JavaScript object that is the result of creating a JavaScript Object Notation (JSON)-serialized representation of the requested .NET Framework type (for example, struct).

If scriptAlias is an unrecognized alias in the scope of the current Silverlight control, this method throws an exception.

If scriptAlias is a reserved string, this method throws an exception. Current reserved strings are are jsonSerializer and downloader.

createObject(string scriptAlias, jsObject initializationData)

Return type: jsObject

Parameters: stringscriptAlias, jsObjectinitializationData

Description: This method provides the same behavior as createObject(scriptAlias), but it JSON-serializes initializationData, and then deserializes the resulting JSON string onto the target .NET Framework type. In other words, this method is a JSON copy operation from a source JavaScript object graph to a target .NET Framework type.

If the underlying type is an array, initializationData can instead be a number (integer) that specifies the ordinality of the array.

Because initializationData can only be either a JavaScript object or a number, the second parameter cannot be used to initialize a typeName from the following primitive JavaScript types: number, Boolean, and string. number is interpreted as ordinality for initializing an empty array. Boolean and string are not JavaScript objects, and they are easily represented in JavaScript variables.

If the JSON copy fails, an exception is thrown.

The jsonSerializer helper has been disabled because an object graph of mixed JavaScript and managed code wrappers currently cannot be serialized. This has been changed to the content.services.jsonSerialize(jsObject) method.

The following special-case version of the createObject method returns the Silverlight JSON serializer:

Return type: jsObject

Parameters: stringscriptAlias = "jsonSerializer"

Description: This is a special-case behavior for the createObject method that returns the Silverlight JSON serializer. The method returns a JavaScript object that has the following methods:

  • string serialize(jsObject) – Provides the same functionality as the ASP.NET AJAX JSON serializer, but with JSON serialization formatting limited to be in sync with the CSD managed JSON serializer. This method also supports JSON serializing script wrappers around managed objects. The default ASP.NET AJAX JSON serializer cannot perform this operation because of the lack of dictionary iterator support on dictionary wrappers that are returned from Silverlight.

  • jsobject deserialize(string) – Given a string, converts it back to a JavaScript object graph. The expectation is that the JSON string was previously generated by using the serialize method. Interoperability with the ASP.NET AJAX JSON serializer will also work with the constraint that deserialize expects JSON strings that are not wrapped with CSRF prevention, as generated by the ASP.NET AJAX JSON serializer. This enables developers to write code such as the following:

    var a = Content.services.createObject("jsonSerializer").serialize(jsObject);
    

Automatic Support for Creating Script Wrappers

Unless you explicitly decide not to use helper methods, the HTML Bridge feature automatically supports creating wrappers for managed types that are exposed through either input parameters or return values on scriptable properties, methods, and events.

Automatic support for creating wrappers is subject to the following rules:

  • Only top-level types that are exposed through input parameters or return values are supported. For example, if an input parameter typed as "Customer" has a scriptable property typed as "Address", only "Customer" is automatically exposed. Types for complex subproperties need to be registered explicitly with the HtmlPage.RegisterCreateableType method.

  • The top-level managed reference types must have public default constructors. The constructor does not have to be marked as [ScriptableMemberAttribute]. The reason for this relaxed constraint is that create methods are intended as helper methods for JSON-serializing JavaScript object graphs onto target .NET Framework types. There is no requirement for the target .NET Framework types to be marked as scriptable if they are only used for JSON deserialization targets.

  • The assemblies that contain top-level types must already be loaded in the target application domain. The HTML Bridge feature will not force the loading of an assembly based on a type that is found in an input parameter or return value.

  • Types that are List<T>, Dictionary<string,V>, or t[] (array of t) are supported. The specific type of the array or generic parameter is specified by using a mini-language that is supported by the create methods. See the method descriptions later in this topic for syntax information.

  • Reference types and structures are supported.

  • If the same base type name is defined in two or more namespaces, and two or more namespace-qualified types are exposed as input parameters or return values of scriptable properties, methods, or events, the type name is not automatically registered. Instead, a developer must explicitly disambiguate the types by using the HtmlPage.RegisterCreateableType method.

The following methods are exposed directly off the registered endpoint for a scriptable object.

createManagedObject (string typeName)

Return type: jsObject

Parameters: stringtypeName

Description: Given the typeName of the target .NET Framework type, this method creates a default instance of the type by using either a parameterless constructor (for reference types) or the default value representation (for value types).

The returned object is a JavaScript object that acts as a wrapper for the managed type. If the managed type is not scriptable (that is, it has no scriptable properties, methods, or events), the returned JavaScript object is a black box (that is, it can be passed back into managed code), but there are no scriptable endpoints exposed to JavaScript callers.

For value types, JavaScript receives either a JavaScript primitive value or a JavaScript object that is the result of creating a JSON-serialized representation of the requested .NET Framework type (for example, structures).

The value of typeName must be one of the following:

  • The name portion of a type. For example, for a type called MyNamespace.Customer, you would use "Customer".

  • For List<T>, typeName is "List<T>". For example, to create a List<MyNamespace.Customer>, you would use "List<Customer>".

  • For Dictionary<string,V>, typeName is "Dictionary<string,V>". For example, to create a Dictionary<string,MyNamespace.Customer>, you would use "Dictionary<string,Customer>". Note that only "Dictionary<string,…>" is supported, because JavaScript has no concept of indexing a dictionary with anything other than string-valued keys.

  • For arrays of T, typeName is just T[]. For example, to create a Customer[5], you would use ("Customer[]", 5). This also means that for arrays, you always have to use the overload of createManagedObject that has two parameters (see the next section). Otherwise, you will end up with an instance of the requested type instead of an array of instances.

  • Lists, dictionaries, and arrays can be composed by using this mini-language. For example, to create a List<Dictionary<string,List<Customer[]>>>, you would use "List<Dictionary<string,List<Customer[]>>>".

For types that are specified by using the mini-language, the supplied type string must exactly match the mini-language strings. In other words, the mini-language is case-sensitive.

For types that are specified by using the mini-language, the supplied type string is both space-insensitive and case-insensitive (it uses invariant culture for case comparison). The HTML Bridge feature strips off excess leading and trailing spaces when it parses the type.

The following shorthand C# style type aliases are supported for primitive types. You must use the friendly variations, not the true type names, for these.

  • int = Int32

  • byte = Byte

  • char = Char

  • bool = Boolean

  • decimal = Decimal

  • float = Single

  • long = Int64

  • sbyte = SByte

  • short = Int16

  • double = Double

  • string = String

  • uint = UInt32

  • ulong = UInt64

  • ushort = UInt16

If typeName is not in the scope of any top-level types that are used as input parameters or return values on the current script endpoint, the createManagedObject method throws an exception.

If typeName is an unrecognized type string or cannot be parsed, this method throws an exception.

createManagedObject (string typeName, jsObject initializationData)

Return type: jsObject

Parameters: stringtypeName, jsObjectinitializationData

Description: This method provides the same behavior as createManagedObject(typeName); however, the method JSON-serializes initializationData and then deserializes the resulting JSON string into the target .NET Framework type. In other words, this method is a JSON copy operation from a source JavaScript object graph to a target .NET Framework type.

If the requested typeName is an array, initializationData can instead be an integer that specifies the ordinality of the array.

Since initializationData can only be either a JavaScript object or a number, the second parameter cannot be used to initialize a typeName from the following primitive JavaScript types: number, Boolean, and string. number is interpreted as ordinality for initializing an empty array. Boolean and string are not JavaScript objects, and can be easily represented in JavaScript variables.

If the JSON copy fails, an exception is thrown.

JSON Serialization Support

Because of the inability to walk JavaScript proxies for managed objects by using a pure JavaScript serializer, the HTML Bridge exposes a helper method for performing serialization of mixed JavaScript and managed object graphs. The default ASP.NET AJAX JSON serializer cannot perform this operation because of the lack of dictionary iterator support on dictionary wrappers that are returned from Silverlight.

Serializing JavaScript/Managed Object Graphs in JavaScript

The following example shows how the services endpoint exposes the jsonSerialize and requiresManagedSerializer helper methods for use by JavaScript code.

content.Services.jsonSerialize(jsObjectGraph) //returns a JSON string

// Indicates whether the root of the object graph is really pointing
// to a managed object in the control's application domain.
content.Services.requiresManagedSerializer(jsObjectGraph) //returns a bool

The jsonSerialize method supports JSON-serializing pure JavaScript object graphs, as well as object graphs that contain, or are rooted with, JavaScript proxies around Silverlight managed objects.

The requiresManagedSerializer method indicates whether the JavaScript variable is actually a proxy pointing back to a managed object in the same application domain as the Silverlight control.

For more information about how the serializer used by jsonSerialize works, see the section on serialization and deserialization rules for ScriptObject.

Deserializing JSON into JavaScript/Managed Object Graphs in JavaScript

The following example shows how the return value from jsonSerialize can be converted back into either a pure JavaScript object graph or a JavaScript proxy around a managed type.

var s = context.services.jsonSerialize(someMixedObjectGraph);
var mixedObjectGraph = content.services.createObject('managedTypeName',s);
var pureJavaScriptObjectGraph = eval(s);

However, the process of serializing using jsonSerialize and then deserializing back to JavaScript is not symmetric. For example, if you serialize a JavaScript object graph with embedded managed object references, the links to those managed objects are lost when the data is converted into a JSON string. There is no way to automatically deserialize from a JSON string back into a JavaScript object graph if the root object is JavaScript, but one or more child objects are managed.

Deserializing from a JSON string back to a JavaScript object where the root object is a JavaScript proxy back to a managed object will work. However, if the underlying managed type has properties of type ScriptObject, those properties can only be deserialized as ordinary JavaScript objects.

The following code converts a JSON string returned by the ASP.NET AJAX JSON serializer into a managed object graph (assuming that the original JavaScript object graph actually conforms to the target managed type).

// Note the use of ".d" to drill into the underlying objects.
// Any embedded _type fields being round-tripped from the server will
// be ignored by Silverlight because __type is meaningless in 
// Silverlight.
var mixedObjectGraph =
 content.services.createObject('managedTypeName', eval(aspnetJson).d);

Serialization and Deserialization Rules for ScriptObject Using DataContractJsonSerializer

Special support was added to enable JSON-serialization and deserialization of managed ScriptObject references when JSON-serializing these types.

When the DataContractJsonSerializer is traversing a managed object graph and encounters an object of type ScriptObject, the DataContractJsonSerializer calls back into the HTML Bridge infrastructure and asks it to provide a JSON string. The HTML Bridge infrastructure then applies the following rules:

  • If the ScriptObject ultimately points back at a managed object graph in the same Silverlight application domain, the HTML Bridge uses DataContractJsonSerializer to serialize that object graph and returns the resulting JSON string. This scenario can occur when the ScriptObject points at a JavaScript object, which, in turn, is really a JavaScript proxy for a managed object in the same application domain.

  • If the ScriptObject points at a JavaScript object that is equivalent to an HtmlObject (or any derivations thereof), an empty JSON string {} is returned. This means that an attempt to serialize DOM objects and a reference to the window object result in an empty JSON string.

  • If the ScriptObject points at a JavaScript object or a JavaScript primitive, the HTML Bridge uses a built-in, JavaScript-based JSON serializer to serialize the object graph. This is the same JavaScript-based JSON serializer that is invoked when calling content.services.jsonSerialize. This JavaScript-based serializer has the same functionality as the ASP.NET AJAX JSON serializer with some important differences:

    • The Silverlight version does not package the object graph in a variable called "d", because the Silverlight serializer is not directly involved in creating the outer JSON used for any network communications.

    • The Silverlight version includes extra checks to properly handle JSON-serializing JavaScript variables that are really JavaScript proxies for managed objects in the same domain as the Silverlight control. For example, if a JavaScript object has the property p1, which is really a JavaScript proxy for a managed dictionary in the same Silverlight application domain, the Silverlight serializer knows to call back into managed code and invoke a DataContractJsonSerializer against the underlying managed object graph for p1.

If a JavaScript variable points at a managed object from a different application domain, the JavaScript serializer just sees it as a regular JavaScript object. However, because a developer cannot serialize managed objects from JavaScript without explicit support from Silverlight (because of the dictionary iterator limitation), the JavaScript serializer in the different-application domain case will just see an empty object. The resulting JSON string will be {}.

When the DataContractJsonSerializer is deserializing a JSON string onto an instance of ScriptObject, it calls back and passes the JSON string to the HTML Bridge. The HTML Bridge simply calls back out into the browser's JavaScript engine and evaluates the JSON string, assigning the resulting JavaScript object as the underlying value of the ScriptObject.

The deserialization process has the following important implications:

  • JavaScript primitives and pure JavaScript object graphs will round-trip as expected.

  • Document Object Model (DOM) objects and other browser objects that conceptually are derivations of HtmlObject are serialized as {}. Upon deserialization, the ScriptObject reference will point at an empty JavaScript object instead of the original DOM object or reference.

  • JavaScript proxies to managed objects will round-trip as deserialized JavaScript object graphs. The managed nature of the original ScriptObject is lost.

See Also

Other Resources