In Windows Phone 7 there was support for using WCF Data Services, but this support was somewhat limited. For example, using the LINQ syntax wasn’t supported, so we had to create our uri’s ourselves. Next to that, we had to generate our classes manually, using the command line and DataSvcUtil.exe. In Windows Phone 7.1 things have changed. We can now use LINQ syntax to create our uri’s and we have support for”Add service reference” in Visual Studio 2010. This article will show how to get started with WCF Data Services and Windows Phone 7.1, which is important as it will be the primary way to access your data from a Windows Phone application.
The Server
In order to get started with WCF Data Services and Windows Phone 7.1, we’ll need to create a WCF Data Service. Here is an overview of my web project, which is hosted in the same solution as my Windows Phone 7.1 application:
This isn’t that exciting. I started out with an empty ASP.NET web project and added an entity data model to it (the .edmx file). I only selected the Product table from the Adventureworks database. After adding the .edmx file, I added a new WCF Data Service to it. Here is the code in the .svc file:
1: using System.Data.Services;
2: using System.Data.Services.Common;
3:
4: namespace ProductServiceHost
5: {
6: public class ProductService : DataService<AdventureWorks2008Entities>
7: {
8: public static void InitializeService(DataServiceConfiguration config)
9: {
10: config.SetEntitySetAccessRule("Product", EntitySetRights.All);
11: config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
12: }
13: }
14: }
As you can see, it’s a normal WCF Data Service which uses my AdventureWorks2008Entities ObjectContext. I won’t go into detail about WCF Data Services at the server, since there isn’t any difference at the server for a Windows Phone client. Just a remark on the side, you have to love the little amount of effort it takes to create a WCF Data Service!
The Windows Phone client
Let’s first take a look at the client application:
As you can see, the client simply shows the products in a listbox. Beneath the listbox there are buttons to perform CUD functionality. If the user selects a product and presses “View”, the UI changes and a few properties can be edited:
When the user presses the save button in the top screen, all the changes (inserts, deletes and updates) will be submitted to the WCF Data Service shown earlier.
Loading data
The first thing to do is to use the new “Add Service reference feature” in Visual Studio 2010:
This will generate our client classes. Now you might think that if you’ve already used the OData client library in the full .Net framework you know what’s coming. This is partially true. The OData client library for Windows Phone uses a somewhat easier programming model. Let’s take a look at where the data is loaded:
1: // Constructor
2: public MainPage()
3: {
4: InitializeComponent();
5: _context = new AdventureWorks2008Entities(new Uri("http://localhost:49753/ProductService.svc/", UriKind.Absolute));
6: _products = new DataServiceCollection<Product>(_context);
7: _context.SaveChangesDefaultOptions = SaveChangesOptions.Batch;
8: _newInstance = true;
9: }
10:
11: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
12: {
13: base.OnNavigatedTo(e);
14:
15: if (_newInstance)
16: {
17: if (State.ContainsKey("productService"))
18: {
19: // need to restore the state.....
20: string stateString = (string)State["productService"];
21: DataServiceState state = DataServiceState.Deserialize(stateString);
22: _context = (AdventureWorks2008Entities)state.Context;
23: _products = (DataServiceCollection<Product>)state.RootCollections["products"];
24: _lbxProducts.ItemsSource = _products;
25: }
26: else
27: {
28: //A new instance with no state saved, load new data....
29: DataServiceQuery<Product> products = (DataServiceQuery<Product>)
30: from p in _context.Product
31: where p.ListPrice < 500
32: select p;
33: Debug.WriteLine("Products uri: " + products.RequestUri);
34: _products.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(HandleProductsLoaded);
35: _products.LoadAsync(products);
36: }
37: }
38: _newInstance = false;
39: }
40:
41: protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
42: {
43: base.OnNavigatedFrom(e);
44: if (e.NavigationMode != NavigationMode.Back)
45: {
46: Dictionary<string, object> collections = new Dictionary<string, object>(1);
47: collections["products"] = _products;
48: try
49: {
50: string state = DataServiceState.Serialize(_context, collections);
51: State["productService"] = state;
52: }
53: catch (SerializationException ex)
54: {
55: Debug.WriteLine("Serialization exception: " + ex.Message);
56: }
57: }
58: }
Don’t worry to much about the if statements and the restoring state parts. Let’s start at 5- 7. At line 5 I create a new DataServiceContext class, which was generated when I added the service reference. This class is the so called spider in the web when you use the WCF Data Service client library. It’s comparable to the ObjectContext class if you’re familiar with the Entity Framework. It contains a DataServiceQuery<Product> object, comparable to an EntitySet of the Entity Framework. You can use this to query the products. Next to query objects for all the EntitySets you expose at the server, the DataServiceContext also contains methods to add, remove and update objects.
Now you might think that you would use the add, remove and update methods on the DataServiceContext to perform modifications and this is possible, but it’s more common the use a DataServiceCollection<> to perform modifications. This DataServiceCollection is instantiated on line 6, and the DataServiceContext is passed in, in it’s constructor.
Let’s skip a few lines to lines 29-35. Line 29 is very interesting. You can see that I use a LINQ query on the Product property of the DataServiceContext. You can always cast this result back to a DataServiceQuery object, which is very useful for debugging. Line 29 is a huge win for Windows Phone 7.1. Constructing a LINQ query that was not executed, but parsed into an expression tree and translated to something else, wasn’t possible in Windows Phone 7. As you might know, this LINQ query is translated to an uri to which a GET request is sent by the DataServiceContext. Because all the information of my query is contained in the uri, this means that the query is handed to my WCF Data Service, which reconstructs it and sends it to the Entity Framework. This means that my data is filtered in the database, while I create a simple LINQ query on the client. This still amazes me :).
You can see the Debug.WriteLine statement on line 33, this is it’s output:
Products uri: http://localhost:49753/ProductService.svc/Product()?$filter=ListPrice lt 500M
After constructing the LINQ query, the DataServiceCollection comes into play. The idea is that you supply a query to it’s LoadAsync() method. The collection executes the query and fires the LoadCompleted event when finished. All of this is happening on lines 34-35. The entities that are the result of the query are added to the collection. This collection implements INotifyCollectionChanged, so it can be used for data binding. Using this collection has a couple of advantages over using the DataServiceContext directly:
- Change Tracking for updates: The DataServiceContext doesn’t do any change tracking and you have to use the UpdateObject() method to indicate that an object is modified. If you use the DataServiceCollection, the collection is responsible for tracking all the objects that it contains. Because the collection also has a reference to the DataServiceContext, it notifies the DataServiceContext of all changes to the collection and the entities in the collection. This means that you can just add,remove and update objects in the collection and when you want to persist the changes you call SaveChanges() on the DataServiceContext.
- UI Thread marshalling. The DataServiceContext doesn’t do any UI Thread marshalling. The DataServiceCollection does, this means that the LoadCompleted event fires on the UI thread and I can just assign the collection to the ItemsSource property of my listbox.
- Easier handling for paging: If you’ve worked with the OData Client library in the full .Net framework you know that when a query is paged on the server, it’s a bit of a pain to get all the pages. This becomes easy with the DataServiceCollection class in Windows Phone thanks to it’s LoadNextPartialSetAsync() method, which adds the next page of entities to the collection.
CUD
As you might have guessed, CUD functionality becomes very simple thanks to the DataServiceCollection, as I can just work with it like with any normal collection. This the handler for the add button:
1: private void HandleAddProduct(object sender, RoutedEventArgs e)
2: {
3: Product p = new Product()
4: {
5: Name = "Change this!" + new Random().Next(),
6: ProductNumber = new Random().Next().ToString(),
7: ModifiedDate = DateTime.Now,
8: SellEndDate = DateTime.Now,
9: SellStartDate = DateTime.Now,
10: ReorderPoint = 1,
11: SafetyStockLevel = 1,
12: rowguid = Guid.NewGuid()
13: };
14:
15: _products.Add(p);
16: _lbxProducts.SelectedItem = p;
17: }
I just set some default values because they are mandatory in the Adventureworks database. On line 15 I add the new product to the DataServiceCollection.
This is the handler for the Delete button:
1: private void HandleRemoveProduct(object sender, RoutedEventArgs e)
2: {
3: _products.Remove((Product)_lbxProducts.SelectedItem);
4: }
This doesn’t need a lot of explanation. Finally the handler for the save button:
1: private void HandleSaveProduct(object sender, RoutedEventArgs e)
2: {
3: _context.BeginSaveChanges(HandleSaveChanges, null);
4: }
5:
6: private void HandleSaveChanges(IAsyncResult result)
7: {
8: Dispatcher.BeginInvoke(() =>
9: HandleSaveChangesForUiThread(result));
10:
11:
12: }
13:
14: private void HandleSaveChangesForUiThread(IAsyncResult result)
15: {
16: try
17: {
18: _context.EndSaveChanges(result);
19: MessageBox.Show("Save completed!");
20:
21: }
22: catch (DataServiceRequestException ex)
23: {
24: DataServiceResponse response = ex.Response;
25: ChangeOperationResponse singleResponse = (ChangeOperationResponse)response.SingleOrDefault((co) => co.Error != null);
26: if (singleResponse != null)
27: {
28: MessageBox.Show("Error: " + singleResponse.Error.Message + ", for product: " + ((Product)((EntityDescriptor)singleResponse.Descriptor).Entity).Name + ", all changes were rolled back at the server");
29: }
30: else
31: {
32: MessageBox.Show("Unknown error: " + ex.Message);
33: }
34: }
35: }
Line 1 is the actual handler for the save button. In this method I call the BeginSaveChanges() method of the DataServiceContext and pass a callback to it. This callback is defined on line 6. These methods follow the normal ASync pattern of the .Net framework. There is one catch… As you’ll probably know, I should call the EndSaveChanges() method in the callback. But the call to EndSaveChanges() and the code after it should run on the UI thread. This is important because if EndSaveChanges() isn’t called on the UI thread you’ll get an exception. This is because the DataServiceContext receives generated values from the server when EndSaveChanges is called (like a generated id) and set’s them on the Product entity that was saved. This will fire the INotifyPropertyChanged event of an entity which will in turn update the UI, which should of course happen on the UI thread. An entity which is generated by the OData client library doesn’t fire INotifyPopertyChanged on the UI Thread! That’s why I call the Dispatcher.BeginInvoke() method on line 8. The method that’s passed in is defined on lines 14-34. I call the EndSaveChanges() on line 18. The rest of the method is pretty much standard WCF DataServices error handling.
Tombstoning
Of course it would be a pity if the user of our nifty application added all those new cool products, changed the price of a couple of others, then the user takes an incoming phone call and our application can be tomstoned, which would delete all the changes the user has made. Luckily, the OData Client Library for Windows Phone has a special api to handle tombstoning. This api can save all our state, including loaded entities and pending changes. This means that the user can just continue with his work when the call has ended. Let’s take a look at the overridden OnNavigatedFrom method, which is also called when the application is tombstoned:
1: protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
2: {
3: base.OnNavigatedFrom(e);
4: if (e.NavigationMode != NavigationMode.Back)
5: {
6: Dictionary<string, object> collections = new Dictionary<string, object>(1);
7: collections["products"] = _products;
8: try
9: {
10: string state = DataServiceState.Serialize(_context, collections);
11: State["productService"] = state;
12: }
13: catch (SerializationException ex)
14: {
15: Debug.WriteLine("Serialization exception: " + ex.Message);
16: }
17: }
18: }
At line 5 I check with the new Windows Phone 7.1 api whether it’s a backwards navigation. If it is, I don’t have to save any state, since this means that either the page is closed and removed from the backstack or the entire application is closed. Either way, the user will expect new fresh data when this page is visited again. If it isn’t, I must prepare for tombstoning. This is what’s happening:
- Line 6: I Instantiate a dictionary, which holds all the DataServiceCollection objects I want to preserve. I add my DataServiceCollection to this on line 7.
- Line8: I use the static Serialize method of the DataServiceState class to turn all the tracking information in the context and all the loaded entities in my collection into a string.
- Line 10: I add this string with all the needed information to the State dictionary of the current page. This means that this string is preserved when the application is tombstoned.
- Line 13: I needed this catch because of a bug in this beta. It turns out that if BeginSaveChanges() caused an exception (example: Network is not available), that even when this exception is correctly handled, the DataServiceContext can’t be serialized anymore. I do hope that this bug get’s fixed in the final release.
Next up is the OnNavigatedTo method, which is also called when my application is restored after tombstoning. Here it is:
1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
2: {
3: base.OnNavigatedTo(e);
4:
5: if (_newInstance)
6: {
7: if (State.ContainsKey("productService"))
8: {
9: // need to restore the state.....
10: string stateString = (string)State["productService"];
11: DataServiceState state = DataServiceState.Deserialize(stateString);
12: _context = (AdventureWorks2008Entities)state.Context;
13: _products = (DataServiceCollection<Product>)state.RootCollections["products"];
14: _lbxProducts.ItemsSource = _products;
15: }
16: else
17: {
18: //A new instance with no state saved, load new data....
19: DataServiceQuery<Product> products = (DataServiceQuery<Product>)
20: from p in _context.Product
21: where p.ListPrice < 500
22: select p;
23: Debug.WriteLine("Products uri: " + products.RequestUri);
24: _products.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(HandleProductsLoaded);
25: _products.LoadAsync(products);
26: }
27: }
28: _newInstance = false;
29: }
On line 5 I check whether the constructor is called. This is necesary because an application doesn’t always become tombstoned after it has been deactivated. It can remain in memory the same as it was left behind if the resources of the device allow it. This is called “Fast App Switching”. If this happens, the OnNavigatedTo fires if the user returns to the application, but the constructor doesn’t fire. So if fast app switching took place, you don’t have to restore your state. By the way, fast app switching is the default in the windows phone emulator when you press it’s Windows button. You can change this on the project properties if you want to test tombstoning:
On line 7 I check if the state was previously saved. This is necesary because OnNavigatedTo of course also fires when the application starts normally. If the state exists I retrieve the string on line 10 from the State dictionary. On line 11 I turn this string into a DataServiceState object by using the static DataServiceState.DeSerialize() method. This DataServiceState object has two properties:
- Context, which contains the DataServiceContext with all the tracking information I serialized earlier.
- RootCollections, this is the dictionary which holds all the DataServiceCollection objects I serialized earlier. I retrieve my “products” DataServiceCollection on line 13.
On line 14 I assign the restored DataServiceCollection to the ItemsSource property of my listbox and with this all state has been restored.
Conclusion
In my opinion the OData Client Library for Windows Phone and WCF Data Services will be the primary way of interacting with your data from your Windows Phone application. This will be the case because the library is the only client technology which supports change tracking in a Windows Phone application and strongly typed LINQ queries which are actually executed on the server. This will be the case at least until WCF RIA Services is supported.
While the api is a little rough around the edges:
- INotifyPropertyChanged isn’t fired on the UI Thread (which isn’t necessarily a bad thing).
- SerializationException occurs when saving state after an exception has been handled.
I’m sure these issues will be solved before the final release. The library is very stable and I encourage you to dig in, because it’s definitely the easiest and fastest way to create CRUD functionality for your Windows Phone 7.1 applications. You can find the example project here.