Exploring the depths and potentials of ASP.NET RSS 2.0 or Subscribe to .BenRush by Email
 Wednesday, May 03, 2006

The profile service in Atlas is implemented in much the same way that the authentication service is - through a dynamically instantiated web service class via a registered HttpHandlerFactory object (see the previous post). The clientside Atlas runtime hooks up with the profile bits on the server using web services, etc asynchronously and in the background. I noticed an interesting bit of coding sugar in Javascript while exploring the profile classes, however, that I thought was particularly interesting. The profile bits are stored inside the Sys.Profile object in an array called "properties". You can get, say, the 'FirstName' from the profile information of the current, executing user like this:

Sys.Profile.properties["FirstName"]

The "cool" part, however, is that you can also access it this way to:

Sys.Profile.properties.FirstName

Now how is this possible?

As it turns out, each object maintains an associative array of fellow objects (an array of associations), so that you can access sub-objects using array indexers or, as you see above, access array indices using standard OO "property" syntax - effectively, they're one in the same. You may have seen this when you try to access the main form of a web page in script, you can do it (if the main form were called Form1) programmatically either way: document.forms['Form1'] or document.forms.Form1; either way gives you a handle to the Form1 object. Syntactic sugar, yes, but it has a very interesting application as I discovered when going back over the code for the authentication script....

....say you wanted to create a method with a dynamic name and a dynamic parameter list. How would you go about it? Well, given that a function is an object in JavaScript, and that we can create objects using associative arrays, then, technically we can simply create a new function using an array-like syntax. If, in Javascript, you wanted to create a proxy object that encapsulated and "felt" like you're procedurally calling methods on a web service so that the syntax of calling a web method looked like this:

Sys.Services.AuthenticationService.login(username.value, password.value, false, OnLoginComplete);

Then you could dynamically create the "login" method at runtime like this:

proxy["login"] = function() {
   ...
   return;
}

You're simply creating a new object at runtime and assigning it to the parent object; it just so happens that our dynamically created object is a function. This is extraordinarily powerful when coupled with automatic discovery of methods and method parameters at runtime - how else could you build a function that felt like calling a web service method when you didn't know, until runtime, what the method would look like?

Now, what about parameters? Bare in mind that you can always access "extra" parameters passed to a javascript function through the arguments object of a function. So, even though the "login" method above has no parameters, if you pass them, then they're accessible to the method implementation through the arguments array:

proxy["login"] = function() {
   var username = arguments[0];
   var password = arguments[1]; 
   
...
   return;
}


Addendum:

This is nothing special with the Atlas framework itself, but is actually a part of the javascript language. Some of my posts relate to simply showing off those features for those who may not know so that the Atlas framework makes more sense when studying it.


kick it on DotNetKicks.com
Wednesday, May 03, 2006 4:00:17 PM (Central Standard Time, UTC-06:00)  #    Comments [2] - Trackback
Computing

Here, check this out:

When watching a Fiddler dump of the http request/response pattern of the Microsoft sample on Http Forms Authentication in Atlas, you see the following resource being requested:

http://atlas.asp.net/docs/atlas/samples/formsandmem_cs/ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx. Have a look:

POST /docs/atlas/samples/formsandmem_cs/ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx?mn=login HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer:
http://atlas.asp.net/docs/atlas/samples/formsandmem_cs/Default.aspx
Content-Type: application/json
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Host: atlas.asp.net
Content-Length: 75
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: ...

Now, what's interesting about this is that if you actually follow the link (try it), you'll see that you're spat back a 404 - not found error from the web server. However, this is the webservice that gets communicated with for all forms authentication; it literally maintains the interface that all of the Atlas framework expects for this authentication mechanism (the login, verification, et al). How can this web service not exist, but be available for communication? When you see a request for a resource that doesn't exist on disk, you immediately become aware that some sort of magic is happening, and typically the party responsible for said magic is either some sort of Isapi module/extension or an HttpModule/Handler. All of the code for ASP.Net Atlas is stored within a single .Net assembly called Microsoft.Web.Atlas; which rules out the ISAPI idea, leaving us with HttpModules/Handlers.

Inspect the web.config file for your Asp.Net Atlas application and take note of the following:

  <httpHandlers>
   <remove verb="*" path="*.asmx"/>
   <add verb="*" path="*.asmx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/>
   <!--
          The MultiRequestHandler enables multiple requests to be handled in one
          roundtrip to the server. Its use requires Full Trust.
      -->
   <add verb="*" path="atlasbatchcall.axd" type="Microsoft.Web.Services.MultiRequestHandler" validate="false"/>
   <add verb="*" path="atlasglob.axd" type="Microsoft.Web.Globalization.GlobalizationHandler" validate="false"/>
   <!--
          The IFrameHandler enables a limited form of cross-domain calls to 'Atlas' web services.
          This should only be enabled if you need this functionality and you're willing to expose
          the data publicly on the Internet.
          To use it, you will also need to add the attribute [WebOperation(true, ResponseFormatMode.Json, true)]
          on the methods that you want to be called cross-domain.
          This attribute is by default on any DataService's GetData method.
         
      <add verb="*" path="iframecall.axd" type="Microsoft.Web.Services.IFrameHandler" validate="false"/>
      -->
   <add verb="*" path="*.asbx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/>
  </httpHandlers>

...I intentionally highlighted the .asmx handler, Microsoft.Web.Services.ScriptHandlerFactory. If you note, the resource being requested is a web service (with the .asmx extension). The way ASP.Net works is to inspect the configuration files (machine.config, web.config) for handlers registered for particular extensions and to insert those handlers into the request/response pipeline. The statement above effectively sticks the Microsoft.Web.Services.ScriptHandlerFactory type into the pipeline for responding to requests for the .asmx extension. Now, this data type exists within the Microsoft.Web.Atlas assembly mentioned earlier, so I cracked open Lutz Roeder's most excellent .Net Reflector and peered inside (yes, I love doing this - in case you haven't noticed).

ASP.Net sees that the type registered for .asmx is the one in the config file, it locates it, loads it into the runtime, JITS it up and requests the method ::GetHandler from the instance it creates. This method is suppose to do inspection on the query string and return the correct HttpHandler object (web form, web service, etc) that is expected to generate the output stream content for the runtime to send back to the client. What gets registered in the config file is either that handler itself (IHttpHandler), or the handler factory (IHttpHandlerFactory). What's registered above is the handler factory. That means this class returns a type that is responsible for handling the .asmx request. What all this boils down to is that the object doesn't really need to exist on disk since, at this point, it's up to the handler to locate and return the resource requested. As it turns out, the type is actually stored within Microsoft.Web.Atlas itself:

/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx resolves to the datatype Microsoft.Web.Services.Standard.AuthenticationWebService housed within the Microsoft.Web.Atlas assembly which inherits from System.Web.Services.WebService (which is a valid http handler that can service the web request). What this means is that the factory class above parses the query string and returns an instance of the right data type to the runtime for execution. The actual method which does all the magic is called GetWebServiceData within the WebServiceData class (also in the Microsoft.Web.Atlas assembly). Decompiled it looks like this:

internal static WebServiceData GetWebServiceData(string virtualPath, bool failIfNoData)
{
   virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
   Type type1 = WebServiceData._mappings[virtualPath] as Type;
   bool flag1 = false;
   if (type1 == null)
   {
      flag1 = HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
      if (flag1)
      {
         type1 = BuildManager.GetCompiledType(virtualPath);
      }
   }
if ((type1 == null) && !flag1)
{
   string text1 = null;
   int num1 = virtualPath.IndexOf("ScriptServices/");
   if (num1 != -1)
   {
      num1 += "ScriptServices/".Length;
      text1 = virtualPath.Substring(num1, (virtualPath.Length - num1) - 5);
      text1 = text1.Replace('/', '.');
      type1 = typeof(WebServiceData).Assembly.GetType(text1, false, true);
      if (type1 == null)
      {
         type1 = BuildManager.GetType(text1, false, true);
      }
     }
     else
      {
         try
         {
            text1 = Path.GetFileNameWithoutExtension(virtualPath);
            text1 = WebServiceData.DecryptString(text1);
            type1 = Type.GetType(text1);
         }
         catch
         {
         }
         if (type1 != null)
         {
            WebServiceData._mappings[virtualPath] = type1;
            WebServiceData._typeVirtualPath[type1] = virtualPath;
         }
      }
     }
   if (type1 != null)
   {
      return WebServiceData.GetWebServiceData(type1);
   }
   if (failIfNoData)
   {
      throw new InvalidOperationException();
   }
   return null;
}


Note the parsing out of the string "/ScriptServices" and the replacement of forward slash (/) with period (.) to get the fully-qualified path to the data type for use in the reflection services of the runtime. The type found in this method eventually makes it way up the call chain to be instantiated and returned from the handler factory to the runtime. The result is that it, then, processes the web service page and, it executes, giving back the proper bits of data for Forms Authentication.

Happy coding,


kick it on DotNetKicks.com
Wednesday, May 03, 2006 2:14:37 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing
 Tuesday, May 02, 2006

Woah! Yesterday my blog got hit 217 times. Thanks.....


kick it on DotNetKicks.com
Tuesday, May 02, 2006 5:05:57 PM (Central Standard Time, UTC-06:00)  #    Comments [1] - Trackback
Computing

Yes, again, I'm digging into these things....if you'd like to see the ASP.Net "Atlas" sample I'm referring to in this blog post, it's located here.

Notice that the only supported form of authentication in Atlas is Forms (meaning that Windows or "Kerberos/NTLM" and Passport authentication are missing). I'm no expert in Kerberos-based authentication, but I imagine that passing along the ticket in a secure manner might be difficult or impractical via JavaScript, and I imagine the Passport authentication scheme might be rather odd too when implemented in JavaScript. Regardless of why, the only valid authentication scheme supported by Atlas at the moment is Forms; meaning you're dealing with cookies (which is simple for JavaScript to access - another reason why this form was probably chosen).

Three methods are supported by the Sys.Services.AuthenticationService class:

  • validateUser()
  • login()
  • logout()

In the next blog post I'm going to explain how these methods work under the hood (within the script), or I'm going to at least try to - but for now, I'm going to simply show how they work programmatically (how you use them). What each function does is rather self-explanatory - login() logs a user in and adds the forms authentication cookie to the browser, validateUser() validates a user's login status by examining the forms authentication cookie and logout() removes the forms authentication cookie from the browser and effectively logs the user out. The whole system utilizes partial postbacks and so, therefore, is done asynchronously with the client experience.

The following script call then,

Sys.Services.AuthenticationService.login(username.value, password.value, false, OnLoginComplete);

This is a Fiddler trace of the process occuring on the Microsoft sample:

REQUEST:

POST /docs/atlas/samples/formsandmem_cs/ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx?mn=login HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer:
http://atlas.asp.net/docs/atlas/samples/formsandmem_cs/Default.aspx
Content-Type: application/json
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Host: atlas.asp.net
Content-Length: 75
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: CommunityServer-UserCookie605740=lv=4/29/2006 8:20:41 PM&mra=5/1/2006 2:18:26 PM; .ASPXANONYMOUS=6mqTPsGZxgEkAAAAOGY3MTdhMWMtNTZkNS00YTVjLTkwMmQtYmYyMzRkZmFkMjAzT5esZ6m9p6Ha5oC1KM8c5LDCmx41; ASP.NET_SessionId=apbic1njfd45gk45x510wa45; .CommunityServer=....

{"userName":"myusername","password":"mypassword!","createPersistentCookie":false}

RESPONSE:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Date: Tue, 02 May 2006 20:52:23 GMT
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Set-Cookie: .CommunityServer=...
path=/; HttpOnly
Vary: Accept-Encoding

true 

Notice the "true" returned on the body of the HTTP response. If the login attempt was a failure, the return would be "false". The login method simple executes asynchronously and, in the completion event handler, lets the code know what this return value was. Your clien-side experience takes suit from this.

Now, arguably, logging in and handling all the validation on the client may not be the best choice to make in all circumstances since there may be data you don't want in the HTML; but for simple UI changes, etc. I can see the benefit of clientside forms authentication. Personally, I see the most benefit in the validateUser() ethod when having the client render itself. Also note that no data is encrypted (the username/password combo passed to the server).


kick it on DotNetKicks.com
Tuesday, May 02, 2006 3:02:17 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing
 Monday, May 01, 2006

I was dissapointed, quite dissapointed actually, when I realized that the sample I was looking at was enabling clientside paging but without partial postbacks to retrieve more data; meaning that even though the client was paging data, it was requesting all of the data initially and paging through it once it arrived. I'm not sure if there is a built-in way around this, but it kind of sucks to think that all the data has to be brought down to the client first. Anyway...again, I got curious about how it all worked....

...it all starts off with a <listView> and a <dataSource> object:

                <dataSource id="dataSource" autoLoad="true" serviceURL="SimpleDataService1.asmx" />
               
                <listView id="dataContents" itemTemplateParentElementId="masterTemplate" >.....

You've seen this before, it's nothing amazingly new. One thing that does strike us as interesting, though, is that the binding for the <listView> control doesn't point directly to the <dataSource> object, but instead points to a new object named "view", and that the datapath is now "filteredData".  

                    <bindings>
                        <binding dataContext="view" dataPath="filteredData" property="data"/>
                    </bindings>

What's going on here is that our binding object is pointing to something called a dataView object, which, in turn has its own dataBind object that points to the dataSource:

                <dataView id="view" pageSize="12">
                    <bindings>       <binding dataContext="dataSource"
                            dataPath="data"
                            property="data"
                            />
                    </bindings>
                </dataView>

The binding path goes like this, then:

Web Service -> DataSource -> Binding -> DataView -> ListView

The reason for all this middle-man object stuff is that the datView provides the paging capabilities for the listView, and therefore presents the listView with a "view" on the data for its binding. The paging capabilities are supported by the dataNavigator object:

<dataNavigator id="pageNavigator" dataView="view"/>

Which hooks itself up to the dataView object via the dataView property. So, up to now, we have the web service and the datasource object which does all the serialization for the JSON response, the binding which binds the dataView to the JSON-created object, and the listview. The dataNavigator sits aside and generates events that tells the DataView to change the data it prevents to the ListView and to therefore refresh the contents of the listView. The system is callback-based, you create buttons that send events to the dataNavigator to go to the next page, previous page, last page, etc. and it responds by calling the proper methods on the dataView object:

this.raiseBubbleEvent = function(source, args) {
   var currentTarget = this.get_parent();
   while (currentTarget) {
      if (currentTarget.onBubbleEvent(source, args)) {
      return;
   }
   currentTarget = currentTarget.get_parent();
}

...look closely and see the reference to this.get_parent(); The routine above is the event called when the following button is clicked:

                <button id="firstPageButton" parent="pageNavigator" command="FIRSTPage">
                </button>

The parent points to the pageNavigator object (declared earlier) and the command is packaged into the args of the onBubbleEvent method. The parent, in this case, is the dataNavigator object whose .onBubbleEvent method looks like this:

this.onBubbleEvent = function(source, args) {
   if (!_data) return false;
   var cmd = args.get_commandName().toLowerCase();
   switch(cmd) {
      case "page":
      var arg = args.get_argument();
      if (arg && String.isInstanceOfType(arg)) {
         arg = Number.parse(arg);
      }
      if (arg || arg == 0) {
         _data.set_pageIndex(arg);
         return true;
      }
   break;

   case "nextpage":
      _data.set_pageIndex(_data.get_pageIndex() + 1);
      return true;

   case "previouspage":
      var idx = _data.get_pageIndex() - 1;
      if (idx >= 0) {
         _data.set_pageIndex(idx);
      }
      return true;

   case "firstpage":
      _data.set_pageIndex(0);
      return true;

   case "lastpage":
      _data.set_pageIndex(_data.get_pageCount() - 1);
      return true;
   }
   return false;
}

The point of this method is to call the appropriate methods on the dataView object to change its view on the data for the listView. This means that if you click the button whose command is to go to the next page, the event is bubbled upwards to the dataNavigator object who calls the dataView object's corresponding "nextpage" case above, which calls _data.set_pageIndex() for the next page via the dataView object. This method refreshes the content of the listView and you, effectively, get the 'next page' of data.

....again, though, this doesn't require any partial postbacks to the server, so even though the paging is quick, it's not nearly as efficient with memory consumption as I had hoped it would be.


kick it on DotNetKicks.com
Monday, May 01, 2006 12:17:13 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

Roundtrips are annoying, particularly when you're doing full postbacks, but they can be annoying for partial ones too; as a result, the guys at MS included a cool control in Atlas called the InitialData control whose responsibility is to package the "initial response" of a web service call into the response http stream for the first request. The thought goes like this: if the client has to wait for the page to load, and then have that page request data from a webservice and load it, then we're still being rather foolish with our client experience, so why not just package what would normally be the initial response directly into the output stream for the page itself? And that is exactly what the InitiaData control does.

The InitialData control is a full-fledged ASP.Net control, meaning it's processed server-side. It looks like this declaratively:

    <atlas:InitialData runat=server id="dataSourceInitialData"
        servicePath="~/atlas/samples/data/SimpleDataService1.asmx"
        ClientDataSourceID="dataSource">
    </atlas:InitialData>

Notice the "hookup" to the dataSource object via the ClientDataSourceID. The page this control was taken from had the following reference on it:

   <dataSource id="dataSource" autoLoad="true" serviceURL="SimpleDataService1.asmx" />

By analyzing the response stream using fiddler of my browser's first request, I see a JSON block inserted inside <initialData> tags which lay between the opening and closing tags for the <dataSource> object:

<dataSource id="dataSourceInitialData">
      <initialData><![CDATA[new Sys.Data.DataTable([new Sys.Data.DataColumn("Id",Number,null,false,true),new Sys.Data.DataColumn("Name",String,"New row",false,false),new Sys.Data.DataColumn("Description",String,"",false,false)],[{"Id":0,"Name":"0Architecture","Description":"0The need to define priority goals and use cases for Architectural planning and structuring."},{"Id":1,"Name":"1Architecture","Description":"1The need to define priority goals and use cases for Architectural planning and structuring."},{"Id":2,"Name":"2Architecture","Description":"2The need to define priority goals and use cases for Architectural planning and structuring."},{"Id":3,"Name":"3Architecture","Description":"3The need to define priority goals and use cases for Architectural planning and structuring."},{"Id":4,"Name":"4Architecture","Description":"4The need to define priority goals and use cases for Architectural planning and structuring."},{"Id":5,"Name":"5Architecture","Description":"5The need to define priority goals and use cases for Architectural planning and structuring."},
..................
</dataSource>

The client script for the <dataSource> object supports a property called "initialData" which, programmatically, gets set to this JSON block by the client Atlas runtime. When the <dataSource> object is initialized, the following script block is run:

    this.load = function() {
        if (_initialData) {
            this.set_data(Sys.Serialization.JSON.deserialize(_initialData));
            _initialData = null;
            return;
        }
   .....

This first statement detects if initialData exists, and if so, prompts the JSON deserializer to come into play and create us an object and set the result to the "data" property. It then sets _initialData to null to prevent further use of this JSON block.


kick it on DotNetKicks.com
Monday, May 01, 2006 10:26:16 AM (Central Standard Time, UTC-06:00)  #    Comments [1] - Trackback
Computing
 Sunday, April 30, 2006

Bear with me, this is going deep.

You'll start to understand why sometimes it's hard for people to get ahold of me on weeknights (or even weekends) because the pursuit of understanding these types of things is pretty high with me. I started in on the Atlas samples this last week, to kind of act as a refresher for some of the things I learned at MIX '06, and couldn't stop asking myself how this worked and how that worked - I have been progressing very slowly through the samples, but have been going very deep into each one of them. The one I'm about to show you I did a couple days ago - but am kind of excited to explain because it really harnesses a lot of what the new Atlas framework is all about. The sample is here (if you wish to follow along).

First, let's take a look at a piece of the XML Script that's to be placed into the practice page:

                <dataSource id="dataSource" autoLoad="true" serviceURL="SampleDataService.asmx" propertyChanged="onChange"/>
               
                <listView id="dataContents" itemTemplateParentElementId="masterTemplate" propertyChanged="onChange">
                    <bindings>
                        <binding dataContext="dataSource" dataPath="data" property="data"/>
                    </bindings>
                    <layoutTemplate>
                        <template layoutElement="masterTemplate"/>
                    </layoutTemplate>
                    <itemTemplate>
                        <template layoutElement="masterItemTemplate">
                            <label id="masterIndex">
                                <bindings>
                                    <binding dataPath="$index" transform="Add" property="text"/>
                                </bindings>
                            </label>
                            <label id="masterName">
                                <bindings>
                                    <binding dataPath="Name" property="text"/>
                                </bindings>
                            </label>
                            <label id="masterDescription">
                                <bindings>
                                    <binding dataPath="Description" property="text"/>
                                </bindings>
                            </label>
                        </template>
                    </itemTemplate>
                    <emptyTemplate>
                        <template layoutElement="masterNoDataTemplate"/>
                    </emptyTemplate>
                </listView>

....but first, I digress: The XML code above is actually downloaded into the client browser verbatim; that's right - it's not parsed/read/interpreted/etc by the ASP.Net server runtime at all but instead sent down to the client as is. It's encapsulated within <script> tags and given the script type of text/xml-script. The browser leaves this alone and does not attempt to process it - so the xml block simply goes down to the client. It is the client code executing within the browser starting when the page loads that actually parses through and builds the required java script objects to layout, in memory, the representation of what you see in XML above. After the objects have been laid out, then the client environment begins to execute and the pieces fall as they may....end of digression....

Follow along the XML script above: the Atlas client framework builds an instance of a listView object, and then begins creating various databinding objects and assigning them to it. If you look closely, you see that immediately before the xml script laying out the structure for the listView object there exists a <dataSource> element that instructs the Atlas client framework to build a datasource instance. Of major importance within the <dataSource> element is the serviceURL attribute pointing to SimpleDataService1.asmx: a webservice page; this is important because all of the data that the listview consumes is actually fetched via this webservice, with the <dataSource> object acting as a proxy, so-to-speak. Now, if you follow the binding path (remember that dataContext is the source object for the binding), you'll understand where my initial question came from:

....Okay, look closely, you see that the following has a dataContext:

                    <bindings>
                        <binding dataContext="dataSource" dataPath="data" property="data"/>
                    </bindings>

Whilst the following does not (yes, I just used the word "whilst" - now get over it):

                     <bindings>
                        <binding dataPath="Description" property="text"/>
                     </bindings>

The logic goes like this: the first binding object binds the listView to the <dataSource> (which is, in turn, bound to the webservice), and the second binding binds one of the item template elements of the listView to the listView itself. In short: HUH? See, it's confusing. We have a lot of binding to bindings to dataSources to webServices to (whatever) going on here and it's kind of bit hard to swallow at first. A whole dump of questions follow: How is it that a binding can go without a dataContext attribute, how is it that the Atlas framework can databind to a webservice, what information is downloaded by the webservice and how, given what we already know about databinding, does the whole damn system work? Arg, right? Yeah - arg. Why is this all important? I dunno' about you - I but ain't debugging this crap until I understand a bit about it; so I decided to have some present pain for future gain.

One of the questions was simple to answer: when the dataContext attribute is missing, it means that 'this' is the datasource, meaning the current object. For situations where templates are utilized, then, an item within the template can bind to the main object (in this case the listView) by simply omitting the dataContext attribute. Look closely and see that the following is synonymous with the second binding statement from above:

                     <bindings>
                        <binding dataContext="dataContents" dataPath="Description" property="text"/>
                     </bindings>

Note that the listView object is named "dataContents".

The real meat of the whole xml script block above, however, is all the databinding that occurs. And, most curiously, the databinding to a webservice. How does this work? Again, I cracked open the Atlas runtime client code and started inspecting. First, I looked at what happens when you tell a <binding> object to bind to a dataPath. If you look back on my previous post, you'll see that I discovered that, eventually, a <binding> object will call into one of two methods depending on whether it's binding to an inside or outside source, and in our example, it's binding to an outside source and therefore the following bit of script gets eventually gets executed:

.....
var
sourcePropertyType = Sys.TypeDescriptor.getPropertyType(propertyObject, propertyName);

var value = this._getTargetValue(sourcePropertyType);
if (value != null) {
   Sys.TypeDescriptor.setProperty(propertyObject, propertyName, value);
}
.....

Here you see that Sys.TypeDescriptor.getPropertyType is called to retrieve particular bits of type information. Eventually, after getting the value of the property being "grepped", we then call Sys.TypeDescriptor.setProperty to complete the databinding. Okay, nice and simple right (well, remember you're also seeing a very small sliver of the complete code)? Anyway.....remember this fact: in order to databind, we must get type information of the object we're binding to and use that type information to access properties (properties are essential to the binding). So, if an item within the itemTemplate of a listView is bound to the listView as a data source, whatever data source listView provides must be an object that exposes properties. Good? Okay....

...the journey continued, then, to discover what the listView was exposing as a data source (for the <bindings> of its itemTemplate to bind to). Well, if you look closely at this line, you see that the data property (which is the data source) of the listView is completely tied to the data property of the <dataSource> object:

                    <bindings>
                        <binding dataContext="dataSource" dataPath="data" property="data"/>
                    </bindings>

Because, remember, that the dataPath is the source property, and property is the destination property of the databinding with the dataContext being the source object. So, the data flow goes like this:

WebService -> ListView -> Items of ListView

Ah - but we say, the DATA of the listview is bound to a webservice - but the webservice, THEN, must have properties for us to BIND to because databinding requires properties (i.e. strongly typed objects). That means that the javascript core of Atlas is viewing this web service as something strongly typed. But, how? The answer is simple, and a friend of mine by the name of JSON showed me (no, I didn't misspell it). JSON stands for JavaScript Object Notation - or basically a lightweight way to express javascript objects in a serialized fashion - therefore, in a fashion that one can take any javascript object, serialize it, send it, and have it deseralized by a waiting browser engine. Basically, it's a data interchange format for javascript. Therefore, if, in theory, our webservice were to return its result in JSON format, the javascript engine could parse it, build an object, and our databinding methods of earlier will "just work". And this is EXACTLY what happens, take a look at the following code snippet from the <dataSource> code in the atlas script runtime: 


      Sys.Net.ServiceMethod.invoke(_serviceURL, "GetData", null,
         {parameters: _parameters, loadMethod: _loadMethod},
         Function.createDelegate(this, onLoadComplete),
         Function.createDelegate(this, ready));

If we follow this call down (namely, inspect the .invoke) method of Sys.Net.ServiceMethod, we see that the service is called asynchronously, and that the completion method attempts to parse the result as a JSON object by eventually executing this method:

this.get_object = function() {
   if (!_resultObject) {
      _resultObject = Sys.Serialization.JSON.deserialize(this.get_data());
   }
   return _resultObject;
}

So....In Summary: The individual items of the itemTemplate for the listview bind to the listview, whose data object is bound to the data object of the <dataSource> object, which makes a request to a webservice and, first, attempts to parse the response as a JSON object and build all the appopriate type information, et al in memory for the binding infrastructure to work properly. Everything just takes suit from there on out. But, don't take my word for it, check out this dump that Fiddler gave me while watching the client do a partial postback to the webservice to retrieve information (note that the Content-Type is JSON, and that the body is a javascript object serialized out for us):

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Date: Mon, 01 May 2006 03:05:04 GMT
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Vary: Accept-Encoding

new Sys.Data.DataTable([new Sys.Data.DataColumn("Id",Number,null,false,true),new Sys.Data.DataColumn("Name",String,"New row",false,false),new Sys.Data.DataColumn("Description",String,"",false,false)],[{"Id":0,"Name":"ListView","Description":"A control to display data using templates."},{"Id":1,"Name":"Window","Description":"A control to display dialogs."},{"Id":2,"Name":"Weather","Description":"A control to display local weather."},{"Id":3,"Name":"New row","Description":"sdf"}])


kick it on DotNetKicks.com
Sunday, April 30, 2006 10:25:40 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

ASP.Net Atlas gives Javascript the concept of a "strong type" in much the same way that .Net strongly types objects: through the use of metadata. Occasionally in the MS core javascript code you may see something that looks like this:

var sourcePropertyType = Sys.TypeDescriptor.getPropertyType(propertyObject, propertyName);

Or, possibly, something like this:

Sys.TypeDescriptor.setProperty(propertyObject, propertyName, value);

This begs the question "what is Sys.TypeDescriptor"? It is very similar to System.Type in the .Net runtime in that an instance of it can encapsulate all relevant information regarding a type (such as its methods, properties, etc.). Under the hood, the .getPropertyType method looks like this:

Sys.TypeDescriptor.getPropertyType = function(instance, propertyName, key) {
   if (instance == null) {
      throw Error.createError('instance is null in TypeDescriptor.getPropertyType');
   }

   if (Sys.ICustomTypeDescriptor.isImplementedBy(instance)) {
      return Object;
   }

   if (key) {
      return Object;
   }

   if ((propertyName == null) || (propertyName.length == 0)) {
      throw Error.createError('propertyName is null');
   }

   var td = Sys.TypeDescriptor.getTypeDescriptor(instance);

   var propertyInfo = td._getProperties()[propertyName];
   debug.assert(propertyInfo, String.format('Property "{0}" not found on object of type "{1}"', propertyName, Object.getTypeName(instance)));

   return propertyInfo.type;
}

The most interesting part in the above method is the call into Sys.TypeDescriptor.getTypeDescriptor, and then the use of the returned type descriptor to _getProperties() and return the .type on the returned property of name 'propertyName'. By following this call chain, you eventually see that, for each object, the method .getDescriptor is being called (so that when you want to get the type information for a particular object, the getTypeDescriptor eventually calls getDescriptor). What gets returned is, an INSTANCE of the following:

Sys.TypeDescriptor = function() {
   var _properties = { };
   var _events = { };
   var _methods = { };
   var _attributes = { };

   this._getAttributes = function() {
      return _attributes;
   }

   this._getEvents = function() {
      return _events;
   }

   this._getMethods = function() {
      return _methods;
   }

   this._getProperties = function() {
      return _properties;
   }
}

This, like, System.Type in the .Net runtime, maintains an internal list of all type information related to an object within the clientside Atlas framework. Objects build this instance when asked for it like this:

this.getDescriptor = function() {
   var td = Sys.Data.DataSource.callBaseMethod(this, 'getDescriptor');

   td.addProperty('data', Object);
   td.addProperty('autoLoad', Boolean);
   td.addProperty('initialData', String);
   td.addProperty('isDirtyAndReady', Boolean, true);
   td.addProperty('isReady', Boolean, true);
   td.addProperty('loadMethod', String);
   td.addProperty('rowCount', Number, true);
   td.addProperty('serviceURL', String);
   td.addProperty('parameters', Object, true);
   td.addProperty('serviceType', Sys.Data.ServiceType);
   td.addMethod('load');
   td.addMethod('save');
   td.addEvent('dataAvailable', true);

   return td;
}

If you see above, the .type of "isReady" is a boolean. Therefore, Sys.TypeDescriptor = System.Type and works in many similar ways.
kick it on DotNetKicks.com
Sunday, April 30, 2006 3:21:39 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

Computers Blogs - Blog Top Sites

Archive
<May 2006>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Blogroll
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2009
Benjamin Rush
Sign In
Statistics
Total Posts: 444
This Year: 0
This Month: 0
This Week: 0
Comments: 128
Themes
Pick a theme: