Sunday, October 31, 2010

Feature Changes in WCF Data Services (OData) for Windows Phone 7 Apps

Chris Koenig [pictured below] posted OData v2 and Windows Phone 7 on 10/30/2010:

imageYesterday at PDC10, Scott Guthrie demonstrated a Windows Phone 7 application that he was building using Silverlight.  During this time, he mentioned that a new OData stack had been released for .NET which included an update to the library for Windows Phone 7. Now you might think that this was just a regular old upgrade – you know… bug fixes, optimizations, etc.  It was, but it also signaled a rewrite of the existing CTP that had been available for Windows Phone 7.  In this rewrite, there are some important feature changes that you need to be aware of, as announced by Mike Flasko on the WCF Data Services Team Blog yesterday:

panoramaLINQ support in the client library has been removed as the core support is not yet available on the phone platform.  That said, we are actively working to enable this in a future release.  Given this, the way to formulate queries is via URIs.

We’ve added a LoadAsync(Uri) method to the DataServiceCollection class to make it simple to query services via URI.

So you can easily support the phone application model we’ve added a new standalone type ‘DataServiceState’ which makes it simple to tombstone DataServiceContext and DataServiceCollection instances

In this post, I’ll go through the first 2 of the changes explain what they mean to you, and show you how to adapt your application to use the new library.  In a future post, I’ll explore the new support for the phone application model and tombstoning.

Where are the new bits?

imageFirst things first – we need to get our hands on the new bits.  There are 3 new download packages available from the CodePlex site for OData:

  1. OData for .NET 4.0, Silverlight 4 and Windows Phone 7 (Source Code, if you need it)
  2. OData Client Libraries and Code Generation Tool (just the binaries, and a new DataSvcUtil tool)
  3. OData Client Sample for Windows Phone 7 (great for learning)

After downloading, don’t forget to “unblock” the ZIP files, or Visual Studio will grouch at you.  Just right-click on the ZIP file and choose Properties from the menu.  If there is a button at the bottom labeled “Unblock”, click it.  That’s it.

Once you get the Client Libraries and Code Generation Tool zip file unblocked, and unzipped, replace the assembly reference in your project from the old CTP version of the System.Data.Services.Client.dll to the new version supplied in this new download.

You’ll also need to re-generate the client proxy in your code using the new version of DataSvcUtil.  Remember how?  Just run this command from a Command Prompt opened to the folder where you unzipped the tools

DataSvcUtil /uri:http://odata.netflix.com/catalog /dataservicecollection /version:2.0 /out:NetflixCatalog.cs

Note: You must include the /dataservicecollection attribute (which itself requires the /version:2.0 attribute) to get INotifyPropertyChanged attached to each of your generated entities.  If you’re not going to use the DataServiceCollection objects as I will, then you might not need this, but I’m using it in my samples if you’re following along at home.

Now you can replace your old proxy classes with these new ones.  Now is when the real fun begins…

Wherefore art thou LINQ?

The most impactful change has got to be the removal of LINQ support by the DataServiceProvider.  From Mike’s post:

LINQ support in the client library has been removed as the core support is not yet available on the phone platform.  That said, we are actively working to enable this in a future release.  Given this, the way to formulate queries is via URIs.

Wow.  I don’t know about you, but I have come to depend on LINQ for almost everything I do in .NET anymore, and this one really hits me square between the eyes. Fortunately, these URI-based queries aren’t too complicated to create, and Mike also points out that they’ve added a new method on the context to make this a bit easier on us:

We’ve added a LoadAsync(Uri) method to the DataServiceCollection class to make it simple to query services via URI.

With my custom URI and this new method, it’s actually almost as simple as before, sans LINQ doing all my heavy lifting.

Surgery time

So – to finish upgrading my Netflix application, I’ve got to make some changes to the existing MainViewModel.LoadRuntimeData method.  Here’s what it looks like from the last implementation:

 image

As you can see, this contains quite a bit of LINQ magic. Unfortunately, that’s going to have to go. The only thing we can really salvage, is the instantiation of the NetflixCatalog class based on the Uri to the main OData service endpoint.  This is important – we don’t use a URI with all the query string parameters in one shot because the DataContext class needs to have a reference to the base URI for the service, and future queries will be based on that base URI.

Rebuild the query

Now that we have a reference to the main service, we now have to build our own query. To do this, we’ll need to manually convert the LINQ query into something that the previous DataContext would have done for us.  There are a couple ways to figure this out. First, we could dive into the OData URI Conventions page on the OData web site and read all about how to use the various $ parameters on the URL string to get the results we want.  The sneaky way, however, would be to run the old code with the old provider and look at the URI that got sent to the Netflix service by snooping on it with Fiddler.   The results are the same – one long URL that has all the parameters included.

 image

Although it’s pretty long, note how clean the URL is – Where becomes $filter, OrderBy becomes $orderby and Take becomes $top. This is what is so great about OData: clean, clear, easy to read URLs.

Armed with our new URI, we can go in and replace the LINQ query with a new Uri declaration. Since we already have the new DataContext based on the service’s URI, we can remove that from here and just use the stuff that comes after:

 image

Don’t forget to mark this new URI as UriKind.Relative.  This new URI is definitely NOT an absolute URI, and you’ll get an error if you forget. Here’s what the new code looks like so far:

 image
ObservableCollection –> DataServiceCollection

Now that the DataContext is created, and the replacement query is built, it’s time to load up the data. But what are we going to load it up into? Depending on which version of my old application you were using, you might see code like I listed above with ObservableCollection or you might have the version that I converted to use the DataServiceCollection.  This new model definitely wants us to use DataServiceCollection as it has some neat ways to manage loading data for us.  That means we will have to swap out our definition of the Items property with a DataServiceCollection.

First, replace instances of ObservableCollection with DataServiceCollection.  Second, remove the initializer for the Items property variable – it’s no longer needed. Third, and this is optional, you can tap into the Paging aspects of the DataServiceCollection by adding a handler to the DataServiceCollection.Loaded event.

Note: I don’t need this feature now, so I’m not going to add code for it.  I’ll leave it as an exercise for the reader,or you can hang on for a future post where I add this back in.

Run the query

Now that my query URIs are defined, and my DataServiceCollection objects are in place, it’s time to wire up the final changes to the new query.  For this, all I have to do is initialize the Items property with a DataServiceCollection and ask it to go run the query for us.

image

Notice the simplified version of the loading process.  Instead of having to go through and manually load up all the items in the ObservableCollection, here the DataServiceQuery handles all that hard work for us.  The main thing we need to remember is to initialize it with the DataContext before calling out to it.

Wrapping it all up

Now that we’ve got everything working, let’s take a look at the whole LoadRuntimeData method:

 image

Except for a few minor changes to the ViewModel properties (all we really did was change a type from ObservableCollection to DataServiceQuery) the actual code changes were pretty minimal. I still don’t like that I have to write my own URL string, but the team is going to address that for me in the future, so I guess I can hang on until then.

I’ve uploaded the project to my Skydrive, so you can download this version to see it in action.  It’s still not a very exciting application, but it does show off how to use the new OData library.  As always, thanks for reading, I hope you found it valuable, and let me know if you have any questions.

Chris Koenig is a Senior Developer Evangelist with Microsoft, based in Dallas, TX. Prior to joining Microsoft in March of 2007, Chris worked as a Senior Architect for The Capital Group in San Antonio, and as an Architect for the global solution provider Avanade.

Chris Woodruff observed in a comment to the preceding post:

To work with the new OData Client Library with developing Windows phone 7 applications there is a hint and two gotcha's:

  • Hint -- If you are a LINQ'er and want to get your URI's written quickly go to my blog post "Examining OData – Windows Phone 7 Development — How to create your URI’s easily with LINQPad" at http://www.chriswoodruff.com/index.php/2010/10/28/examining-odata-how-to-create-your-uris-easily-with-linqpad/
  • Gotcha -- Seems the ability to use the Reactive Framework has been broken with this new update of the OData Client Library. To do Async loads of data calls on the DataServiceCollection<t>, you need to have an observer tied to your class like most MVVM patterns show. Rx is different as it allows Observers to subscribe to your events which does not seem to be handled in the code for the DataServiceCollection<t> written for WP7.
  • One more Gotcha -- If you have the new Visual Studio Async CTP installed on your machine you will not be able to compile the source of the OData Client Library that is on the CodePlex site. It has variable names of async that conflict with the new C#/VB.NET keywords async and await.


No comments:

Post a Comment