Silverlight Tip: How to reflect ScriptObject content in runtime

July 15, 2008

Today I want to show how Silverlight application could “understand” which objects form HTML DOM from hosting page it deals with. The minute before I start, let me show why many of Silverlight developers need it.

Lets assume we have some simple HTML/ASPX page, with some JavaScript functionality and JavaScript objects, like follows:

   1: <html xmlns="http://www.w3.org/1999/xhtml" style="height:100%;">
   2: <head runat="server">
   3:     <title>Test Page For ScriptReflector</title>
   4:         
   5: <SCRIPT language="JavaScript">
   1:  
   2:     //Some JS variables...
   3:     var BOARD   = {};
   4:     var elmBoard;
   5:     var b = BOARD;
   6:     
   7:     //JS Function, which will intialize JS variables, but also will call SL functionality
   8:     function foo()
   9:     {
  10:         elmBoard = document.getElementById("Xaml1");
  11:     
  12:         b.TestPropery = "Alexg@sela.co.il";
  13:         b.extObj  = elmBoard;
  14:         b.extDoc  = b.extObj.document;
  15:         b.extBody = b.extDoc.body;
  16:         b.extWin  = b.extDoc.parentWindow;
  17:         
  18:         //Execute SL functionality
  19:         var sl = document.getElementById("Xaml1").content;
  20:         sl.SL.DoSomething(b);
  21:         
  22:     } 
  23:     

</SCRIPT>

   6:     <style type="text/css">
   7:         #Text1
   8:         {
   9:             width: 300px;
  10:         }
  11:     </style>
  12: </head>
  13: <body style="height:100%;margin:0;">
  14:     <form id="form1" runat="server" style="height:100%;">
  15:         <input id="Text1" type="text" value="Some HTML text is here..."/><input id="Button1" type="button" value="Click Me!" onclick="foo();" />
  16:         <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
  17:         <div  style="height:80%;">
  18:             <asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/ScriptReflector.xap" MinimumVersion="2.0.30523" Width="100%" Height="100%" />
  19:         </div>
  20:     </form>
  21: </body>
  22: </html>

image

When user clicks on the button, it does something with JavaScript objects, and then executes some Silverlight function (Silverlight class was already marked as [ScriptableType] and corresponding function was marked as [ScriptableMember]). Assuming we trying to debug Silverlight application and inspect the received object, what we will see?

image

Not very informative…

But what if we need to know what is in this ScriptObject? Which properties it has? Which functions? Could it be done?

Sure it could be done… Lets start by adding two simple functions to our JavaScript section:

   1: //ScriptReflector Helper function - for Properties Reflection  
   2: ReflectProperties = function(obj)
   3: {
   4:     var props = new Array();
   5:     
   6:     for (var s in obj)
   7:     {
   8:         if (typeof(obj[s]) != "function")
   9:         {
  10:             props[props.length] = s;
  11:         }
  12:     }
  13:     
  14:     return props;
  15: }
  16:  
  17: //ScriptReflector Helper function - for Functions Reflection
  18: ReflectMethods = function(obj)
  19: {
  20:     var methods = new Array();
  21:     
  22:     for (var s in obj)
  23:     {
  24:         if (typeof(obj[s]) == "function")
  25:         {
  26:             methods[methods.length] = s;
  27:         }
  28:     }
  29:     
  30:     return methods;    
  31: }

Those functions will help us to “reflect” JavaScript properties at runtime, and they also could be executed from managed code.

Now lets have two managed code functions, which will execute those JavaScript helper functions:

   1: //Properties reflector
   2: public static T ReflectProperties<T>(ScriptObject obj)
   3: {
   4:     T retVal = default(T);
   5:  
   6:     ScriptObject properties = HtmlPage.Window.Invoke("ReflectProperties", obj) as ScriptObject;
   7:     if (null != properties)
   8:         if (int.Parse(properties.GetProperty("length").ToString()) > 0)
   9:             retVal = properties.ConvertTo<T>();
  10:  
  11:     return retVal;
  12: }
  13:  
  14: //Methods reflector
  15: public static T ReflectMethods<T>(ScriptObject obj)
  16: {
  17:     T retVal = default(T);
  18:  
  19:     ScriptObject methods = HtmlPage.Window.Invoke("ReflectMethods", obj) as ScriptObject;
  20:     if (null != methods)
  21:         if (int.Parse(methods.GetProperty("length").ToString()) > 0)
  22:             retVal = methods.ConvertTo<T>();
  23:  
  24:     return retVal;
  25: }

We almost done. I’ve added function, which will use one of those functions and will return Dictionary<string, object>:

   1: //Deep reflection for ScriptObject proprties
   2: //recursionDepth & maxRecursionDepth are two int variables to control recursion depth 
   3: //in case we will want to get complex properties of JavaScript object
   4: public static Dictionary<string, object> DeepReflectProperties(ScriptObject obj)
   5: {
   6:     Dictionary<string, object> retVal = new Dictionary<string, object>();
   7:  
   8:     string[] props = ReflectProperties<string[]>(obj);
   9:  
  10:     if (null != props)
  11:     {
  12:         foreach (string prop in props)
  13:         {
  14:             if (obj.GetProperty(prop) is ScriptObject)
  15:             {
  16:                 if (recursionDepth < maxRecursionDepth)
  17:                 {
  18:                     recursionDepth++;
  19:                     retVal.Add(prop, DeepReflectProperties(obj.GetProperty(prop) as ScriptObject));
  20:                     recursionDepth--;
  21:                 }
  22:             }
  23:             else
  24:                 retVal.Add(prop, obj.GetProperty(prop));
  25:         }
  26:     }
  27:  
  28:     if (retVal.Count == 0)
  29:         retVal = null;
  30:  
  31:     return retVal;
  32: }

All left to do is to execute those functions. To do it, I’ve built simple Silverlight Page, with TextBox for input HTML object element IDs, and button to start reflection of the HTML element properties. In my case I’ve bounded result of DeepReflectProperties function to some ListBox, and ReflectMethods to some TextBox value.

Here is result for  “Button1” (see topmost HTML block):

image

“+”/”-” buttons controls the recursion level.

Execution of HTML Button with “b” JavaScript object (see topmost HTML block) gives following output:

image

image

We done – got properties and method of JavaScript objects in runtime. Now we could use this information to communicate with hosting HTML page more efficient.

 

Full sources here

 

Enjoy,

Alex

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. michaelJanuary 31, 2009 ב 8:13

    I am concerned about performance of the suggested solution. I would expect ScriptObject to provide methods to do the reflection or even present JS properties as properties (that way I could have used them directly in Binding)

    My question: What is preferable- define my own JS data structure that I always know about and use this knowledge in parsing the data in the managed code or use the reflection (as described in the article) to make managed code more flexible?

    Reply
  2. Alex GoleshJanuary 31, 2009 ב 9:37

    Michael,

    Performance will be better if you will know exactly what your ScriptObject has and create more efficiant but less flexible code.

    Regards,
    Alex

    Reply