Connecting to the Service
Except for a couple of platform-specific edits, the code that you'll have to write to interact with the OData service is the same as in Part 3. In the code-behind file for the main page, write the code shown in Listing 4, which is responsible for establishing a connection to the service passing credentials and for keeping in memory the information retrieved from the service.
Listing 4Connecting to the OData service and declaring class-level variables.
'The following directives are required 'Imports System.Data.Services.Client 'Imports MobileOrdersClient.OrdersServiceReference 'Store the service URL. Replace localhost with your server's name Private Shared ReadOnly ServiceUri As _ New Uri("http://192.168.1.3/ExposingOData/ApplicationData.svc") 'Two collections for storing the list of entities exposed by the service Private Property orders As DataServiceCollection(Of Order) Private customers As DataServiceCollection(Of Customer) 'Represent the LightSwitch's intrinsic data Private applicationData As OrdersServiceReference.ApplicationData Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs) If Me.applicationData Is Nothing Then Me.applicationData = New OrdersServiceReference.ApplicationData(ServiceUri) End If 'Pass credentials of a LightSwitch user Me.applicationData.Credentials = New NetworkCredential("TestUser", "TestUser$") 'Create instances of collections to store data Me.orders = New DataServiceCollection(Of Order)(applicationData) Me.customers = New DataServiceCollection(Of Customer)(applicationData) 'Declare event handlers for handling loaded data AddHandler Me.orders.LoadCompleted, AddressOf orders_LoadCompleted AddHandler Me.customers.LoadCompleted, AddressOf customers_LoadCompleted 'Load customers and orders loadCustomers() loadOrders() End Sub
The code still uses the DataServiceCollection(Of T) class, which allows for storing collections of objects exposed by a WCF Data Service. As a reminder, this class inherits from ObservableCollection(Of T), which means that it implements the necessary infrastructure to send and receive notifications of changes to the items it contains. The first difference from other client types is that in WinRT you don't place the code that creates an instance of the service inside the constructor; instead, you place this code in the OnNavigatedTo event handler, which is raised when the user navigates to this page. Of course, a check is required to ensure that an instance of the service isn't already alive. The code accomplishes this with the first If block in the event handler, which avoids generating a second instance if one already exists.
The next step is writing code that loads data, shown with comments in Listing 5.
Listing 5Loading data asynchronously.
'Querying the data service for the full list of items 'and loading data asynchronously Private Sub loadCustomers() Dim query = From cust In Me.applicationData.Customers Order By cust.CompanyName Descending Select cust customers.LoadAsync(query) End Sub 'Alternative syntax using the service URL Private Sub loadOrders() orders.LoadAsync(New Uri("http://localhost/ExposingOData/ApplicationData.svc/Orders", UriKind.Absolute)) End Sub Private Async Sub orders_LoadCompleted(sender As Object, e As LoadCompletedEventArgs) 'If the feed exposes paged data, load the next chunk of data If e.Error Is Nothing Then If Me.orders.Continuation IsNot Nothing Then Me.orders.LoadNextPartialSetAsync() Else 'Otherwise simply assign the data collection as the 'data source of the ContentPanel grid Me.ContentPanel.DataContext = Me.orders End If Else Dim dialog As New Windows.UI.Popups.MessageDialog(e.Error.Message) Await dialog.ShowAsync End If End Sub Private Async Sub customers_LoadCompleted(sender As Object, e As LoadCompletedEventArgs) If e.Error Is Nothing Then 'No assignment of the customers collection 'because it will not be displayed in the UI If Me.customers.Continuation IsNot Nothing Then Me.customers.LoadNextPartialSetAsync() End If Else Dim dialog As New Windows.UI.Popups.MessageDialog(e.Error.Message) Await dialog.ShowAsync End If End Sub
A few more considerations are required when talking about WinRT and Windows 8. As for Windows Phone, in Windows 8 Store Apps every operation must be asynchronous, meaning that the user interface must never be blocked and must always remain responsive even in the case of long-running operations. The code that loads and queries data is already asynchronous, so the code written in Part 3 can be used here. However, in WinRT, message dialogs must be asynchronous as well. For this reason there's no MessageBox object, as you would have in any other .NET development platform. If you want to tell users that something went wrong, you have different alternatives, such as toast notifications or the Windows.UI.Popups.MessageDialog object, which is easier to implement. This is an asynchronous object, so you must invoke its ShowAsync method, together with the Await operator. Also, the method that calls an asynchronous operation must be decorated with the Async modifier, as required by the Asynchronous Programming Pattern.
The final step is writing code that executes some operations over data, such as adding, removing, and saving orders. Listing 6 shows this code.
Listing 6Executing data operations.
Private Sub InsertButton_Click_1(sender As Object, e As RoutedEventArgs) 'Hard coding the creation of a new order 'Assuming there is already a customer with the specified name Dim customerInstance = customers.Where(Function(c) _ c.CompanyName.ToLower.Contains("del sole")).FirstOrDefault Dim newOrder As New OrdersServiceReference.Order With newOrder .OrderDate = Date.Today .Description = "Order of Natural Water" .ShippedDate = Date.Today.AddDays(1) .RequiredDate = Date.Today.AddDays(2) .Customer = customerInstance End With Me.orders.Add(newOrder) End Sub Private Sub DeleteButton_Click_1(sender As Object, e As RoutedEventArgs) Dim currentOrder = TryCast(Me.OrdersListView.SelectedItem, OrdersServiceReference.Order) If currentOrder IsNot Nothing Then Me.orders.Remove(currentOrder) End If End Sub Private Sub SaveButton_Click_1(sender As Object, e As RoutedEventArgs) 'Start saving asynchronously Me.applicationData.BeginSaveChanges(SaveChangesOptions.Batch, AddressOf OnChangesSaved, Me.applicationData) End Sub Private Async Function OnChangesSaved(result As IAsyncResult) As Task ''Use the dispatcher to run the operation inside the appropriate thread 'Get the instance of the application data Me.applicationData = CType(result.AsyncState, ApplicationData) Dim errorMessage As String = "" Try 'Finalize the save operation Me.applicationData.EndSaveChanges(result) 'Reload data for data-binding loadOrders() Catch ex As DataServiceRequestException errorMessage = String.Format("Data service error: {0}", ex.Message) Catch ex As Exception errorMessage = String.Format("Error: {0}", ex.Message) End Try If errorMessage <> "" Then Dim dialog As New Windows.UI.Popups.MessageDialog(errorMessage) Await dialog.ShowAsync End If End Function
The code simulates an insert operation by hard-coding a new instance of an order, rather than providing the user with the ability to specify an order. This design is intended to make the example simpler, but of course in real-world applications you would provide a specific page with text boxes and other controls. About the save operation, notice how the code stores error message in a variable (if any) and based on the content of the variable it invokes the asynchronous message dialog. This technique is required since the Await operator cannot be used inside a Try..Catch block, so you need a way to call it from the outside. Apart from this, the code is the same as in Part 3.