19
Dec
07

ADO.NET Data Services Part 6: AJAX

Phillip gets a call today from a customer:

“Philly my boy, I love the service you setup. This data is going to jump start my website immediately! You know what though, I’ve been trying to get into this AJAX stuff, and as such I was curious whether or not that would work with your service? I also wanted to thank you for dinner last night, I had no idea how sweet you could be.”

Alright, so that call was half weird (alright, a lot weird), but half intriguing. Phillip had to ponder for a minute whether or not his cell phone data could in fact be consumed with JavaScript. He had been spending so much time and effort figuring out how to write client code in .NET (about 2 articles worth), he completely forgot about the possibility of customers wanting their web sites to consume his data client-side.

The ASP.NET AJAX client library does in fact contain the Sys.Net.WebRequest type, and since an ADO.NET Data Service is exposed through a simple REST interface, you could get away with using it to make simple service requests. That seems like a pretty poor option, as we’ve already gotten comfortable writing client code that had semantic value for working with data services (i.e. WebDataContext). Luckily for us we weren’t the only ones thinking that. The ADO.NET team got together with the ASP.NET AJAX team and said “Let’s merge our two worlds together and make something awesome!”

There is a new namespace in the ASP.NET AJAX client library: Sys.Data. This new namespace houses one particular object of interest to us: DataService. “What exactly is DataService and what can it do for me?”, you say. It is pretty much everything you’d need from a lightweight, JavaScript abstraction over a data service. Behind the scenes it’s actually using WebRequest, but at least we don’t have to worry about those details :)

“How do I get the DataService object into my app?” You simply add a script reference to your ScriptManager like so:

snag-0008.jpg

That will add the reference to the ADO.NET Data Service client library, and make the DataService object available for use.

“How did I use the Data Service?” Just like the WebDataContext, the DataService’s constructor takes the URL of the service:

astoria71.JPG

Pretty simple huh? Indeed. “How do you get data out of it?”

snag-0009.jpg

Notice a pattern? Consuming a data service from JavaScript is really easy.

The four parameters the DataService’s query method takes are pretty self-explanatory: the data service query, a method that will be called upon success, a method that will be called upon failure, and an arbitrary piece of context data. The failure callback and context data are optional, so if you really trust your query and/or don’t need any context, you could certainly omit them :)

The query parameter uses the same syntax that we’ve already gone over in previous articles, so there is nothing new going on here. The DataService object calls the specified data service, uses the specified query, and then when complete, will call the success or failure method based on what happened, passing the context data, if provided. It doesn’t get much easier than that.

Ok great, so we make make the above query call, now what? Well we have to implement the actual methods for our success/failure callbacks. The signature for each take three parameters: the query result, the context data (if specified), and the actual query itself.

Our success method looks like so:

snag-0012.jpg

In the case of the success method, the result parameter would be the data requested from the service call. It could be in the form of a collection of objects, a single object, or a scalar property value, depending on what your query was. The context parameter would hold whatever was passed to the context parameter in the call to the DataService’s query method. If null was passed, or the parameter was omitted, then the context parameter of the success method would be null. In our case, the context parameter would be the string “Context Data”, because we passed that in when we called service.query(). The operation parameter is the query you made that succeeded. In this case it would be “Carriers?$orderby=Name”.

The context and operation parameters are more for diagnostic purposes, and probably won’t be used all that much, and as such can be omitted from the method signature if you’d like. The result parameter is the primary focus here. As was mentioned previously, the result parameter will contain whatever data it is we requested, and can be categorized into three types:

  1. Collection of objects
  2. Single object
  3. Scalar property value

If your query is targeting an entity set, like we are doing above (Carriers?$filter=Name), then the result parameter will contain an array of objects. The object type will correspond to whatever the service model object is. In this case it would be Carrier objects. Using the above query, I could modify my success method like so (notice I’ve omitted the context and operation parameters to show how that could look):

snag-0014.jpg

This would result in an alert message reading “Alltel”. As you can see, I’m accessing the carrier through an index on the result parameter (which in this case is just a simple array). I could just as easily enumerate through the list, and perform whatever actions I needed to with the returned data. The possibilities are endless, and also out of the scope of this article. The point is that you can see how amazingly easy I can write JavaScript code to perform an AJAX call to a data service, and then receive my requested data.

What if I modified my query like so (I’ve omitted the failure callback and context parameters to show how that could look):

snag-0015.jpg

In this case, the result parameter passed to the success method wouldn’t be an array of carriers, but instead a single instance of a carrier. I could modify my success method code like so:

snag-0016.jpg

Notice that I no longer have to access the carrier via an index on the result parameter, because it’s receiving a single carrier. Nice and clean.

Now what if I modified the query like so?:

snag-0017.jpg

In this case, the result parameter passed to the success method wouldn’t be an instance of a carrier, but simply the requested carrier’s name. I could modify my success method code like so:

snag-0018.jpg

You might be wondering why I just wasted all that time to illustrate something that probably could have been deduced by someone with half a brain? Because I wanted to make it crystal clear how flexible the DataService object is. When making AJAX calls, you know that bandwidth and processing time are important, so you should be able to request/work with only the data you absolutely need. With the DataService object you aren’t forced to pull down extra bloat, you can streamline your data access as granular as you need.

So what about the failure method? It takes the same three parameters as the success method: result, context, operation. The context and operation parameters are identical to their success method counterparts. The only difference in the failure method over the success method is that the result parameter isn’t the requested data, but rather an object that explains why the query failed.

As I mentioned above, the query method’s failure callback and context parameters are optional. What I didn’t mention is that you can set default values for them on the DataService, which will be used every time you call query and omit those parameters. I could use the following code:

snag-0020.jpg

This would obviously fail due to my gross misspelling of the word “Carriers”. Notice I didn’t pass a failure callback to the query method, which would normally mean that the error would get “swallowed”, but because I set a default failure callback, onFailure will be called. Also notice that I omitted the context data parameter, but I set a default context value of “Sweet1″, which will be passed to the call of the onFailure method when this query fails. You can begin to see the value of the context parameter now knowing that you can use a common failure method accross all queries. The context parameter can well, add context to the query that failed.

Aside from the query method not requiring a failure callback or context parameter, you can also omit a success callback. Obviously it wouldn’t make much sense to omit the success callback, since your call to query would become completely pointless. But, just like the failure callback and context data, you can set a default success callback as well, like so:

snag-0022.jpg

Notice my call to the query method takes only the actual data query, all the other parameters come from the default set on the DataService. In some situations this would make the code much cleaner, but in others it might encourage some nastyness. The point is to use what makes sense for your scenario. Note, that the query method can still accept all of its parameters, which would then override whatever default values had been set. Hence, you could imagine a nice setup with default callbacks and context, that would only be used for query calls that could be generically handled, while other query calls had their own tailored callbacks.

If you don’t want to let the query call hang forever, you can set a timeout on the DataService, which will kill the query call after the specified amount of time (in milliseconds), and then call the failure method. The following code would set a 1 second timeout on the DataService:

snag-0024.jpg

Now every query call made on that instance of the DataService would be limited to a second of processing time.

Remember when I mentioned that the result parameter passed to the failure method contained information about why the query failed? That parameter is actually an object of type Sys.Data.DataServiceError. That object is perfect for determining whether the query failed because of an actual exception, or simply a timeout, since it has a property called timedOut. I could modify the failure method like so:

snag-0027.jpg

Also notice the use of the message property, which provides us with the underlying exception message. The DataServiceError also has the following properties for determining the cause of the error: exceptionType, statusCode, and stackTrace. The exceptionType property returns the name of the exception type that actually occurred (i.e. System.OutOfMemoryException). The statusCode property returns the HTTP error that occured (i.e. 404). The stackTrace property returns what it sounds like, the stack trace that led to the query failure.

At this point Phillip feels pretty proud of himself and all that he had discovered about using ADO.NET Data Services together with AJAX. Just as he is about to call his client back to tell him the good news, he realizes something: XMLHttpRequest (and JavaScript for that matter) can’t make cross-domain calls! If his client wants to access his cell phone data via JavaScript, he’ll have to make his own service that interfaces with the cell phone service, then he can use the DataService object to access his local service. Not exactly ideal, but it shouldn’t cause to much pain. With the said, it should be noted that the above samples and newly introduced classes are meant only for accessing data services within the same domain. That constraint shouldn’t really come as a surprise, since we already had to come to grips with that with web services within our application, data services are no different.

You may be thinking “Aha! I see what this is good for! All those times that I created a web service in my web application purely for the sake of calling it using ASP.NET AJAX to get and update data could now be replaced with a data service! That would allow me to create the same user-experience, but without having to write all that data-centric logic myself! Eureka!” You would be a pretty smart person if you said that, and that is exactly the use of the DataService that I would recommend :) A data service is by no means meant to be exposed over the web and usable by other people’s scripts. It is meant to compliment its parent web application, in the same domain.

The DataService has a couple more methods, but they pertain to change modification, something that we haven’t covered yet. In our next article we’re going to jump back to writing .NET data service client code to go over how we can add/update/delete data from a data service. We’ll also cover how to achieve the same functionality in JavaScript using the client-side DataService object.


6 Responses to “ADO.NET Data Services Part 6: AJAX”


  1. 1 Timothy Khouri Dec 20th, 2007 at 12:34 pm

    This is probably a stupid question, but since ASP.NET AJAX restricts going outside of your own domain (for security reasons), does ADO.NET Data Services with AJAX do the same thing?

    In your example you used a relative path, but if you were to do a: ‘new Sys.Data.DataService(”http://www.singingeels.com/Top10Authors.svc”)’ would you get anything back?

    (If it’s locked down, then I think Philip’s beloved customer is going to be a little disappointed… seeing how beautiful it is, but knowing it’s out of his reach… like how most people feel about Claire Bennet)!

    Thanks,
    -Timothy

  2. 2 Jonathan Carter Dec 20th, 2007 at 3:37 pm

    Hey Timothy, you are exactly right, and I forgot to mention this. I added a new paragraph (second to last) to wrap up the story correctly :)

  1. 1 ASP.NET 3.5 Extensions Preview « See Joel Program Pingback on Dec 29th, 2007 at 10:33 pm
  2. 2 ADO.NET Data Services Part 8: Data Modification - AJAX at Lost In Tangent Pingback on Jan 4th, 2008 at 8:09 pm
  3. 3 ADO.NET Data Services Part 9: Data Modification - AJAX (Action Sequences) at Lost In Tangent Pingback on Jan 6th, 2008 at 2:53 am
  4. 4 ADO.NET Data Services AJAX client library at Lost In Tangent Pingback on May 15th, 2008 at 7:24 am

Leave a Reply




December 2007
S M T W T F S
« Nov   Jan »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Categories