MOBILE

Programming .NET Framework 3.5 : Using Data Synchronization Services (part 2) - Completing the Service & Completing the Client

2/27/2012 9:49:25 AM
2.2. Completing the Service

Our application is being developed, in part, as a learning tool. To help us present the completion of each individual project as clearly as possible, we are going to separate each project into its own Visual Studio 2008 solution. There is no technical need for doing this, but it does make it easier to focus in on the Data Synchronization Services project first and then the Smart Device client project second.

We begin our work on the service by removing two files that came with the WCF Service project template, as they are not needed for a Data Synchronization Service. We mentioned at the time we added the project that we would be removing them. So, we now delete the IService1.cs and Service1.cs files from the project.

What we now are interested in is the code in the CustCache.SyncContract.cs file. In writing this file, the Configure Data Synchronization Wizard did three things: It made one wrong assumption about our consuming client application, it tried to help us transition the service from a WCF Service to a Data Synchronization Service, and it had to specify a default URL. To complete our service application, we need to address all three.

First, the wrong assumption: The wizard did not know that our client would be a Smart Device client, and that a Smart Device client must use XML serialization when passing objects to/from the service. We need to specify XML serialization, and we do that by adding an [XmlSerializerFormat()] code attribute to the ICustCacheSyncContract definition that is located at the end of the CustCache.SyncContract.cs code file, as shown in Listing 1.

Listing 1. The Attributed ICustCacheSyncContract Class
   [ServiceContractAttribute()]
   [XmlSerializerFormat()]
   public interface ICustCacheSyncContract
   {

      [OperationContract()]
      SyncContext ApplyChanges(
                     SyncGroupMetadata groupMetadata,
                     DataSet dataSet,
                     SyncSession syncSession);

      [OperationContract()]
      SyncContext GetChanges(
                     SyncGroupMetadata groupMetadata,
                     SyncSession syncSession);

      [OperationContract()]
      SyncSchema GetSchema(
                    Collection<string> tableNames,
                    SyncSession syncSession);

      [OperationContract()]
      SyncServerInfo GetServerInfo(
                        SyncSession syncSession);
   }

Second, the generated code to help transition the service from a WCF Service to a Data Synchronization Service: The project’s App.Config file contains elements that are applicable to a WCF Service, not to a Data Synchronization Service. At the top of the CustCache.SyncContract.cs code file, as comments, are the suggested replacements that the Configure Data Synchronization Wizard has provided for us. The instructions in the comments tell us to uncomment two elements and replace the corresponding elements in the App.Config file with the uncommented elements.

As we do so, we’ll need to make two changes. We will need to replace the default URL with our service’s actual URL. We must do this, for localhost is meaningless within a Smart Device application (the wizard’s third miscalculation). In our case,

http://localhost:8080/CustCacheSyncService/

becomes

http://192.168.254.2:1824/CustCacheSyncService/

And because we must support Smart Device clients, we must use basic HTTP binding rather than Web Services HTTP binding. Thus,

binding="wsHttpBinding"

becomes

binding="basicHttpBinding"

Once we do so, we have a valid App.Config file.

We also have completed the Data Synchronization Service. We can now turn our attention to our Smart Device client project.

2.3. Completing the Client

When we changed the binding specification of our service to basicHttpBinding, it became a conventional Web service, something that .NET Compact Framework applications have been communicating with for some time now. And almost always, we enable that communication in our Smart Device application by adding a Web reference to the Web service. To add a Web reference, the service must be running, so we go to our service project and run it.

Note

It is important that your client program not be running at this time. If it is, you will be unable to add a Web reference to your client project. If you are still developing both projects within the same solution, running the service will cause the client to be deployed. If that happens, you will need to cancel that deployment.


Once the service is up and running, a default client program is automatically started, a portion of which is shown in Figure 7.

Figure 7. The Test Client Program Displayed


We are interested in the URL displayed on the second line in the window. It should be exactly what we specified in the baseAddress element of the App.Config file, suffixed with “mex” (which indicates the location for metadata exchange and which you need to ignore when setting the reference). As we saw earlier, in our case this URL is http://192.168.254.2:1824/CustCacheSyncService/.

Thus, we turn to our client project and add a Web reference. In the Add Web Reference dialog, we specify the URL exactly as we specified it in the service’s baseAddress element, beginning with “http” and ending with the trailing “/”. Then we click on the Go arrow, browsing to the service and receiving the service description page, shown in Figure 8, in return.

Figure 8. Setting the Web Reference

We choose a meaningful name for our Web reference and add it to our project. Once it has been added, we expand the files in our Solution Explorer window, as seen in Figure 9, to show the generated proxy code file, Reference.cs.

Figure 9. Solution Explorer Showing Generated Proxy Files


Like other files before it, this file also needs to be modified.

When the proxy code within Reference.cs was generated, one class definition that we need was written, as were several that we do not need. To make a long story as short as we can: The methods of our Data Synchronization Service pass parameters of data types that are defined in the Microsoft.Synchronization namespaces. As an inevitable result of proxy code generation, these classes are redefined as they are added to the proxy class definition; specifically, they are redefined as carriers of data only. That is, all method definitions are lost. Since we wish to use the Microsoft.Synchronization versions and their defined methods, we must delete the generated versions from the proxy code, now and anytime we regenerate the proxy code!

The one class that we do not want to delete should be easy to find. It inherits from SoapHttpClientProtocol, should be the first class in the code file, and should have a name that ends with SyncService. So, open Reference.cs and look for the following line of code:

public partial class CustCacheSyncService

We delete all other classes and add the following using statement at the top of the file:

using Microsoft.Synchronization.Data;

We do not need to add references to the Microsoft.Synchronization libraries, for the Configure Data Synchronization Wizard has already done that for us. When it comes to dealing directly with Visual Studio, its templates, and its wizards, we are done. What is left for us to do is write the functionality of our application.

We’ll add just enough functionality so that we can walk through the following scenario.

  1. Synchronize data with the server.

  2. Make some nonconflicting changes at the device and on the server.

  3. Synchronize data with the server.

  4. Make some conflicting changes at the device and on the server.

  5. Synchronize data with the server.

  6. After each synchronization, examine results, data, and errors.

We begin by opening the form in design mode and then adding a full-screen docked DataGrid control plus a two-entry menu to it, as shown in Figure 10.

Figure 10. The Main Form and Its Controls


Then we turn to the form’s code file. We’ll need to add the following: namespace declarations for synchronization, for our proxy classes, and for our typed data set; a routine to modify some customer rows; a routine to synchronize our changes with the server; and code to display the results of that synchronization.

We add the namespace declarations at the start of the code thusly:

using Microsoft.Synchronization.Data;
using SyncClnt.WSCustSync;
using SyncClnt.NorthwindDataSetTableAdapters;

namespace SyncClnt
{
   public partial class FormData : Form

In the form’s Load event we create a typed data set and a table adapter for later use, like so:

private void FormData_Load(object sender,
                           EventArgs e)
{
   dsetNorthwind = new NorthwindDataSet();
   daptCust = new CustomersTableAdapter();
}

To modify some data at the device, modifications that will then need to be synchronized with the server, we iterate through the rows of the Customers data table. Every customer whose CustomerId starts with the letter A will have “** ” injected into the start of his CustomerName field. This code appears in Listing 2.

Listing 2. The Data Modification Routine
private void mitemModifyData_Click(object sender,
                                   EventArgs e)
{
   Cursor.Current = Cursors.WaitCursor;
   foreach (NorthwindDataSet.CustomersRow rowCustomer
            in dsetNorthwind.Customers)
   {
      if (rowCustomer.CustomerID.StartsWith("A"))
      {
         rowCustomer.CompanyName =
            "** " + rowCustomer.CompanyName;
      }
   }
   daptCust.Update(dsetNorthwind.Customers);
   Cursor.Current = Cursors.Default;
}

Listing 3 shows the routine for doing the synchronization of our changes with the server’s data store, via the service. It is well annotated. Any class name that contains the word Cust was written by one of the wizards that we walked through. Any class name that contains the word Sync, but not the word Cust, is defined in one of the Microsoft.Synchronization libraries that the project references.

Listing 3. The Data Synchronization Routine
private SyncStatistics SyncCustomers()
{
   SyncStatistics syncStats;
   try
   {  // Create a proxy object for the synch service.
      //    Use it to create a generic proxy object.
      ServerSyncProviderProxy prxCustSync =
          new ServerSyncProviderProxy(
             new CustCacheSyncService());

     // Create the sync agent
     //    for the Customer table.
      //    Assign the proxy object to it.
      //    Set it for bi-directional sync.
      CustCacheSyncAgent syncAgent =
         new CustCacheSyncAgent();
      syncAgent.RemoteProvider = prxCustSync;
      syncAgent.Customers.SyncDirection =
         SyncDirection.Bidirectional;

      // Sync changes with the server.
      syncStats = syncAgent.Synchronize();

      // The above call to syncAgent.Synchronize
      //    retrieved all Customer table changes
      //    that had were made at the server
      //    between the previous call and this
      //    call and placed them in our
      //    SQL Server CE Northwind database here
      //    on the device.
      // We need to display that table to
      //    our user.  We will use a data bound
      //    data table to do so.
      daptCust.Fill(dsetNorthwind.Customers);
      dgridCustomers.DataSource =
         dsetNorthwind.Customers;
   }
   catch (Exception exSync)
   {
      MessageBox.Show(exSync.Message);
      throw;
   }
   return syncStats;
}

					  

Note that the routine first creates a proxy object for our service and then uses that proxy object in the creation of another, more generic, proxy object. Then the SyncAgent is created and is passed a reference to the generic proxy object. Once any other SynchAgent properties have been set, the agent knows what needs to be synchronized and what service to use to do it. The agent’s Synchronize method then does the synchronization and returns the results in a SyncStats structure.

The SynchAgent object is the keystone object of data synchronization. Although it is located on the device, you want to think of it as sitting between the server-side data store and the client-side SQL Server CE database. Anything that your application needs to do at runtime to influence or alter the behavior of a synchronization will be done by invoking methods of, or setting properties of, or responding to events raised by the SynchAgent object or one of its contained objects.

For example, later in this demo we will need to obtain information about rows that failed to synchronize. We will do that by handling an event that is raised by an object that is contained within the SynchAgent object.

One last comment on our synchronization code: Immediately after the synchronization, we load the rows of the SQL Server CE’s Northwind Customers table into the typed data set’s Customers table. Normally, this is not a good idea. The data set contains the data that we attempted to upload to the server. After synchronization, the SQL Server CE database contains the data that was received from the server. If the synchronization of a row failed, the client-side version will be in the data set table; the server-side version will be in the SQL Server CE table. We might want access to both as we are reconciling the differences.

In the menu selection that initiates the synchronization, we do some UI setup, call our synchronizing routine, and display the results. Listing 4 shows this code.

Listing 4. The Synchronization Menu Handler
private void mitemSynchronize_Click(object sender,
                                    EventArgs e)
{
   Cursor.Current = Cursors.WaitCursor;
   SyncStatistics syncStats = SyncCustomers();
   MessageBox.Show(
      string.Format(
              "{1} {2}.{0}" +
              "{3} {4}.{0}" +
              "{5} {6}.{0}" +
              "{7} {8}.{0}",
              Environment.NewLine,
              "Rows sent - total",
              syncStats.TotalChangesUploaded,
              "Rows sent - succeeded",
              syncStats.UploadChangesApplied,
              "Rows received - total",
              syncStats.TotalChangesDownloaded,
              "Rows received - succeeded",
              syncStats.DownloadChangesApplied));
   Cursor.Current = Cursors.Default;
}

					  

That’s it for adding code to our application. Now it’s time to run the application and see what happens.

We start by running the application and selecting Synchronize from the menu, twice if necessary, to ensure that the data on the device and on the server are identical, as shown in Figure 11.

Figure 11. The Device Synced with the Server


Now we use our data modification routine to make changes to our client version of the data; see Figure 12. And we use SQL Server’s Management Studio to make nonconflicting changes to the data on the server. We insert a few $s into the CompanyName column of the first two B customers, as shown in Figure 13.

Figure 12. Pending Client-Side Changes


Figure 13. Pending Server-Side Changes


Now we can synchronize. Once again we select our Synchronize menu selection. This time we receive the response shown in Figure 14.

Figure 14. Viewing the Nonconflicting Results


From Figure 13, we can see that our four modified rows were sent to the server, modified at the server, and echoed back to us. Why were our modified rows returned to us? For the same reason that so many of you have written code that immediately retrieved newly inserted/updated rows. Inserted/updated rows often contain values that were supplied not by the client but rather by the database engine. System-assigned primary keys, GUID columns, default values, and trigger-generated values are all examples of this. Rather than the client having to ask for the newly inserted/modified row, the service returns the row to the client automatically.

Also, we can see that the rows that were added by some other client, in this case Management Studio, are also delivered to our client, as we would expect.

Now let’s try some conflicting updates. First, we set the data back to what it was when we began our demo. Again we synchronize, twice if necessary, to ensure that the data in both locations is identical. (Refer back to Figure 11.) As before, we modify data on the device by running the data modification routine. (Refer back to Figure 12.) But this time, in Management Studio, we modify two of the same rows that we are concurrently modifying on the device, as shown in Figure 15. When we synchronize the device, we receive the results shown in Figure 16.

Figure 15. Modifying the Two Rows


Figure 15. Viewing the Conflicting Results


Of the four rows that were returned to our device, two are the two rows that we sent to the server and were accepted at the server, including any values that were added on the server as the row was being inserted/updated. The other two are the two rows that we sent to the server that were not accepted at the server. What is sent back to the device is the row as it now exists on the server. That is, we now have the server-side version of the row, which we can present to the user to see whether she wants to resubmit the same update.

Other  
  •  # BlackBerry Java Application Development : Networking - HTTP Basics
  •  Mobile Phone Game Programming : Analyzing 2D Sprite Animation
  •  Mobile Phone Game Programming : Understanding Animation
  •  Synchronizing Mobile Data - Using Merge Replication (part 2) - Programming for Merge Replication
  •  Synchronizing Mobile Data - Using Merge Replication (part 1) - Using Good Design to Avoid Synchronization Failures
  •  Windows Phone 7 Advanced Programming Model : Advanced Data Binding (part 4) - Data Bind to Anything
  •  Windows Phone 7 Advanced Programming Model : Advanced Data Binding (part 3) - Showing Progress & Lazy Load Images
  •  Windows Phone 7 Advanced Programming Model : Advanced Data Binding (part 2) - Syndicated Services
  •  Windows Phone 7 Advanced Programming Model : Advanced Data Binding (part 1)
  •  Beginning Android 3 : The Input Method Framework - Fitting In
  •  Mobile Application Security : Mobile Geolocation - Geolocation Methods & Geolocation Implementation
  •  Mobile Application Security : SMS Security - Application Attacks & Walkthroughs
  •  iPad SDK : Popovers - The Stroke Width Popover
  •  iPad SDK : Popovers - The Font Size Popover
  •  Beginning Android 3 : The Input Method Framework - Tailored to Your Needs
  •  Beginning Android 3 : Working with Containers - Scrollwork
  •  Mobile Application Security : SMS Security - Protocol Attacks (part 2)
  •  Mobile Application Security : SMS Security - Protocol Attacks (part 1)
  •  Mobile Application Security : SMS Security - Overview of Short Message Service
  •  iPad SDK : Popovers - The Font Name Popover (part 2)
  •  
    Video
    Top 10
    Nikon 1 J2 With Stylish Design And Dependable Image And Video Quality
    Canon Powershot D20 - Super-Durable Waterproof Camera
    Fujifilm Finepix F800EXR – Another Excellent EXR
    Sony NEX-6 – The Best Compact Camera
    Teufel Cubycon 2 – An Excellent All-In-One For Films
    Dell S2740L - A Beautifully Crafted 27-inch IPS Monitor
    Philips 55PFL6007T With Fantastic Picture Quality
    Philips Gioco 278G4 – An Excellent 27-inch Screen
    Sony VPL-HW50ES – Sony’s Best Home Cinema Projector
    Windows Vista : Installing and Running Applications - Launching Applications
    Most View
    Bamboo Splash - Powerful Specs And Friendly Interface
    Powered By Windows (Part 2) - Toshiba Satellite U840 Series, Philips E248C3 MODA Lightframe Monitor & HP Envy Spectre 14
    MSI X79A-GD65 8D - Power without the Cost
    Canon EOS M With Wonderful Touchscreen Interface (Part 1)
    Windows Server 2003 : Building an Active Directory Structure (part 1) - The First Domain
    Personalize Your iPhone Case
    Speed ​​up browsing with a faster DNS
    Using and Configuring Public Folder Sharing
    Extending the Real-Time Communications Functionality of Exchange Server 2007 : Installing OCS 2007 (part 1)
    Google, privacy & you (Part 1)
    iPhone Application Development : Making Multivalue Choices with Pickers - Understanding Pickers
    Microsoft Surface With Windows RT - Truly A Unique Tablet
    Network Configuration & Troubleshooting (Part 1)
    Panasonic Lumix GH3 – The Fastest Touchscreen-Camera (Part 2)
    Programming Microsoft SQL Server 2005 : FOR XML Commands (part 3) - OPENXML Enhancements in SQL Server 2005
    Exchange Server 2010 : Track Exchange Performance (part 2) - Test the Performance Limitations in a Lab
    Extra Network Hardware Round-Up (Part 2) - NAS Drives, Media Center Extenders & Games Consoles
    Windows Server 2003 : Planning a Host Name Resolution Strategy - Understanding Name Resolution Requirements
    Google’s Data Liberation Front (Part 2)
    Datacolor SpyderLensCal (Part 1)