Monday, September 17, 2012

WCF Data Services with Silverlight

Last week I wrote a post about Introduction to WCF Data Services and OData and I said I will write a post showing how to access an OData feed from a Silverlight application and this is it.

I will start off by creating a Silverlight Application and I am creating a new web project to host my Silverlight application. I have NuGet installed in my Visual Studio and since I need OData library, in the Package Manager Console I am running the following command.
PM> Install-Package Microsoft.Data.Services –Pre
oData0
Package Manager Console
oData0.1
Install OData Library
And then I can see the following references are added to my web project.
  1. Microsoft.Data.Edm
  2. Microsoft.Data.OData
  3. Microsoft.Data.Services
  4. Microsoft.Data.Services.Client
reference0.2
References
Now I am adding a ADO.NET Entity Data Model to my web project (right click and add new item ) and I am naming it as DBEntities. Please note that I have a sample table to store Customer information and has following columns.
  1. CUSTOMER_ID (int) – Primary Key
  2. CUSTOMER_FIRST_NAME (string)
  3. CUSTOMER_LAST_NAME (string)
DBEntities3
ADO.NET Entity Data Modal : Choose Data Connection
Tables4
ADO.NET Entity Data Modal : Choose Database Objects

Now I am adding a WCF Data Service to my web project and I am naming it as DBService.
DBService6
Add WCF Data Service

I will be getting something like this.
Untitled1
Modify WCF Data Service
I can see some errors, because there is ambiguous reference and that’s because System.Data.Services and System.Data.Services.Client are included in OData library. So I need to delete   System.Data.Services and System.Data.Services.Client from references.
referenceremove0.3
Remove References
Now I am modifying the class as follows.
   public class DBService : DataService<DBEntities>
   {
       // This method is called only once to initialize service-wide policies.
       public static void InitializeService(DataServiceConfiguration config)
       {
           // TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
           // Examples:
           config.SetEntitySetAccessRule("CUSTOMERs", EntitySetRights.All);
           // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
           config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
       }
   }
In here I have put DBEntities as “ /* TODO: put your data source class name here */ ” and I have uncommented config.SetEntitySetAccessRule method. Here “CUSTOMERs” is a ObjectSet created for my table “CUSTOMER”. You can check it by examining the code file of your created ADO.NET Entity Data Model. I have changed EntitySetRights.AllRead to EntitySetRights.All, because I am planning to Read and Write to the entity.

Now I am pretty much done modifying the items in the web project and now I am starting to modify my Silverlight Application.

First I will add a Service Reference to the DBService in my web project.

Untitled2
Service Reference
Now I am modifying my MainPage.xaml. I have created some controls.

Untitled3
Sample Application
Here there is a Busy Indicator in the top, a Combo Box, two Text Boxes and three buttons for Adding, Updating and Deleting. My requirement is I can Add a customer, I should be able to list existing customers in combo box and I should be able to Update/Delete selected customer.

My combo box is binded and other controls are just there. XAML code for binding the combo box is,
<!--I am binding the combobox and the display field should be customer ID-->
        <ComboBox Height="23" ItemsSource="{Binding}" DisplayMemberPath="CUSTOMER_ID" HorizontalAlignment="Left" Margin="101,109,0,0" Name="cboCustomer" VerticalAlignment="Top" Width="165" SelectionChanged="cboCustomer_SelectionChanged" />
Now I am writing the code in the code behind of the MainPage.xaml. Rather explaining one by one, I am pasting the code here with comments.
using System;
using System.Data.Services.Client;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using WCFDataServicesSilverlightApp.svcDBService; // service referencenamespace WCFDataServicesSilverlightApp
{
    public partial class MainPage : UserControl
    {
        // DataServiceContext
        DBEntities oDBEntities;

        // dynamic entity collection of customers
        DataServiceCollection<CUSTOMER> resultColl;

        // relative path of the service
        private const string ServiceUri = "/DBService.svc";

        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            // in Page load I am retrieving the existing customer list and 
            // filling up my combo box.
            LoadCustomers();
        }

        private void LoadCustomers()
        {
            // create the DataServiceContext using the service URI.
            oDBEntities = new DBEntities(new Uri(ServiceUri, UriKind.Relative));

            // linq query to retrieve all the customers.
            var query = from c in oDBEntities.CUSTOMERs
                        select c;

            resultColl = new DataServiceCollection<CUSTOMER>();

            // I am loading the collection asynchronously because I don't need
            // my page to be stucked while loading customers.
            resultColl.LoadAsync(query);

            // while loading the customer I am showing a busy indicator.
            busyIndicator1.IsBusy = true;

            // event to be raised when the loading is completed.
            resultColl.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(ResultColl_LoadCompleted);

            // DataContext for binding the combobox.
            DataContext = resultColl;
            cboCustomer.DataContext = DataContext;
        }

        private void cboCustomer_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            oDBEntities = new DBEntities(new Uri(ServiceUri, UriKind.Relative));

            // when selected index is changed, I am retrieving the information of the
            // selected the customer.
            var query = from c in oDBEntities.CUSTOMERs
                        where c.CUSTOMER_ID == ((CUSTOMER) cboCustomer.SelectedValue).CUSTOMER_ID
                        select c;

            resultColl = new DataServiceCollection<CUSTOMER>();

            // I am loading the collection asynchronously.
            resultColl.LoadAsync(query);

            // event to be raised when the loading is completed.
            resultColl.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(ResultColl_LoadCompleted);
        }

        void ResultColl_LoadCompleted(object sender, LoadCompletedEventArgs e)
        {
            // loading completed.
            busyIndicator1.IsBusy = false;

            // if loading is cancelled or if there is an error show some message.
            if (e.Cancelled || e.Error != null)
            {
                MessageBox.Show("Error loading customer.");
            }
            else
            {
                if (resultColl != null)
                {
                    if (cboCustomer.SelectedValue != null)
                    {
                        // setting up the information.
                        txtFirstName.Text = resultColl[0].CUSTOMER_FIRST_NAME;
                        txtLastName.Text = resultColl[0].CUSTOMER_LAST_NAME;
                    }
                }
            }
        }

        private void btnAdd_Click(object sender, RoutedEventArgs e)
        {
            CUSTOMER customer;

            // adding a customer.
            // Create always needs a Primary Key.
            // Since my ID column is an Identity Column in SQL, ID is auto generated and auto incremented
            // I am passing some integer value.
            customer = CUSTOMER.CreateCUSTOMER(-1);

            // set property values.
            customer.CUSTOMER_FIRST_NAME = txtFirstName.Text;
            customer.CUSTOMER_LAST_NAME = txtLastName.Text;

            AddRecord(customer);    
        }

        private void btnUpdate_Click(object sender, RoutedEventArgs e)
        {
            // get selected customer.
            CUSTOMER customer = resultColl[0];

            // changing the property values.
            customer.CUSTOMER_FIRST_NAME = txtFirstName.Text;
            customer.CUSTOMER_LAST_NAME = txtLastName.Text;

            UpdateRecord(customer);
        }

        private void btnDelete_Click(object sender, RoutedEventArgs e)
        {
            // get selected customer.
            CUSTOMER customer = resultColl[0];

            DeleteRecord(customer);
        }

        private void AddRecord(CUSTOMER customer)
        {
            // add the new customer to the customer entity set.
            oDBEntities.AddToCUSTOMERs(customer);

            // asynchronously start saving and OnChangesSaved will be called when completed.
            oDBEntities.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, oDBEntities);
        }

        private void UpdateRecord(CUSTOMER customer)
        {
            // update selected customer in the customer entity set.
            oDBEntities.UpdateObject(customer);

            // asynchronously start saving and OnChangesSaved will be called when completed.
            oDBEntities.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, oDBEntities);
        }

        private void DeleteRecord(CUSTOMER customer)
        {
            // delete selected customer from customer entity set.
            oDBEntities.DeleteObject(customer);

            // asynchronously start saving and OnChangesSaved will be called when completed.
            oDBEntities.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, oDBEntities);
        }

        private void OnChangesSaved(IAsyncResult result)
        {
            // this will be showed when asynchronous BeginSaveChanges is completed.
            MessageBox.Show("Done.");
        }
    }
}
And that's it. Hope this helps.

Happy Coding.

Regards,
Jaliya