software solutions for a mobile world

Building a Sync Services for ADO.NET Solution for Mobile Devices

Andy Wigley

*Update* :This is the first in a series of posts on Sync Services.
You can read the second post on batching here: Sync Services: Implementing batching to reduce data transfer volumes.

In my recent webcast '24 Hours of Mobile Development: Networking', I showed a sample that used Microsoft Synchronization Services for ADO.NET to synchronize a local cache database on a Windows Mobile device with backend SQL Server via a WCF service. I didn't really go into detail on how to build it, and I've had a few enquiries since, so this is how you do it (This is also what I showed at one of my sessions at Tech Ed EMEA in Barcelona in November):

Setup your environment

First you need to get the right bits for this to work. You need:

Note that Visual Studio 2008 already includes Sync Services and SQL Server 3.5 SP1 - but only for desktop clients, so you have to install these bits for devices separately.

Install the Database

Download the sample code for this article (link at the bottom of the page), and use the instnwnd.sql script that is included to create a Northwind database in your database server. Easiest way to do this is to open SQL Server Management Studio, connect to your database server, then use Windows Explorer to navigate to where instnwnd.sql is and double-click it. The script will open in SQL Server Management Studio, then you just have to click the Execute icon in the toolbar and the database will get created. If you are using SQL Server Express, I recommend you first download SQL Server Express Management Studio from MSDN and install it, then use the same technique to execute the database creation script.

Create the Solution

The key thing here is to create a solution containing two projects - one which is a devices project to contain the client code, and one which is a WCF Service Library or an ASP.NET Web Service which contains the server-side code. In this example, I'll walk you through creating a solution which uses a WCF Service on the server side.

  1. Start Visual Studio 2008 (use 'Run as Administrator' if you're developing on Vista).
  2. Create a Smart Device project, call it 'SmartDeviceSyncClient' or something else suitable. You can use any Windows Mobile 5 or Windows Mobile 6 target platform and .NET Compact Framework 3.5. Make it a 'Device Application' though so it is a Windows Forms application.
  3. Now add a second project to the same solution. Make this a WCF Service Library project with the name 'MiddleTierServiceLibrary' - or your own name.
  4. When the WCF Service Library has been added, delete IService1.cs and Service1.cs - we are going to use the Sync Services wizards to generate the WCF Service interface and implementation.

Run the Sync Designer

This is the bit that confuses most people. Normally, you start the Sync Services Designer by adding a new project item of type Local Database Cache to the client-side project, but this template is not available in device projects. The way you do this is actually to add it to the server-side project, which seems a bit illigical, but works just fine:

  1. In Solution Explorer, right-click the MiddleTierServiceLibrary project, then click Add - New Item. Select Local Database Cache and give it a suitable name, such as  NorthwindCache.sync.
  2. In the Configure Data Synchronization wizard, the first thing you must do is use the Server connection dropdown to select an existing database connection to the backend database server where the Northwind database is, or click New to create a new connection.  The wizard will automatically generate the client connection for you.
  3. Next click the Add button underneath the Cached Tables pane. Here is where you select which tables to copy to the client database cache. For this example, just select the Customers table. In the Data to download dropdown, select the default, which is New and incremental changes after first synchronization. This means that Sync Services will only transfer changes between the client and server when synchronizing - the alternative is to transfer the entire table every time.

  4. After you click OK, you return to the main Sync Designer wizard screen. Here you must click the Advanced button to reveal additional configuration options. Make sure you select the MiddleTier ServiceLibrary for the Server Project location, and the device project, SmartDeviceSyncClient, as the Client project location.

  5. Now click OK, and the wizard will generate the code for synchronization between the SQL Server Compact 3.5 SP1 database on the client side, and the SQL Server database on the server side. It also does an initial sync and adds a SQOL Server Compact 3.5 SP1 database to your client-side project which already has the Customer table data copied across.
  6. Next, it displays the Data Source Configuration Wizard. This is prompting you to setup a typed DataSet in your client-side project, because it thinks you'll need to have something in that project to use in order towork with the data in the client-side database. You can cancel that if you have your own ideas on what you're going to do with the data once it gets to your client-side application, but for this demo a typed DataSet is fine, so click the Tables checkbox to select all tables (there's only one of course, the Customers table) and accept the default name of NorthwindDataSet, and then click Finish.

Setup the WCF Service

Think that's all there is to it? No, not at all! The Sync Designer does a lot of the setup work for you, but you have to do quite a lot more work to get this working.

  1. Open up NorthwindCache.SyncContrct.cs - you'll find it in the server-side project.
  2. Scroll down to the bottom, and add the XmlSerializerFormat attribute to the INorthwindCacheSyncContract interface definition. This is necessary because the .NET Compact Framework does not support the WCF DataContractSerializer which is the default used by WCF when serializing data for transfer over the network, so adding this attribute ensures that the XmlSerializer is used, which is supported by .NET Compact Framework 3.5.


        [ServiceContractAttribute()]
        [XmlSerializerFormat()]
        public interface INorthwindCacheSyncContract {
           
            [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);
        }
  3. Now scroll up to the top of this file. You will see a large comment block which tells you how to setup the configuration file for the WCF service.
  4. First, copy the <service>..</service> definition from the comment block, and paste it into the app.config file, replacing the existing <service>..</service> configuration. You need to remove the c# comment characters of course, but there are two other changes that are important:
    • First, change the binding="wsHttpBinding" to binding="basicHttpBinding" - .NET Compact Framework doesn't support wsHttpBinding.
    • Second, change the localhost:8080 in the baseAddress configuration to the proper host name and port where the service will be running, for example 'myPC:8088', or you can use the raw IP address, for example '192.168.1.8:8080' - the important thing here is to use an address that the device will be able to use to connect to your server; 'localhost' is no good because the device will interpret 'localhost' as being itself, where of course the WCF service is not running.
  5. Now go back to the code file and copy the <behavior>..</behavior> configuration. Paste this into App.Config replacing the existing definition. No changes are required, other then to uncomment it of course.
    The app.config should now look something like this:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      ...
      <system.serviceModel>
        <services>
          <service name="MiddleTierServiceLibrary.NorthwindCacheSyncService" behaviorConfiguration="MiddleTierServiceLibrary.NorthwindCacheSyncServiceBehavior">
            <host>
              <baseAddresses>
                <add baseAddress ="http://192.168.1.104:8088/NorthwindCacheSyncService/"/>
              </baseAddresses>
            </host>
            <endpoint address ="" binding="basicHttpBinding" contract="MiddleTierServiceLibrary.INorthwindCacheSyncContract"/>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="MiddleTierServiceLibrary.NorthwindCacheSyncServiceBehavior">
              <serviceMetadata httpGetEnabled="True" />
              <serviceDebug includeExceptionDetailInFaults="False" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    </configuration>

Adding the Service Reference

 The server-side code is good to go now, so we need to add a service reference to the client-side project.

There is a trick to doing this successfully with a devices project. If this was a desktop client, we could just right-click on the project and click 'Add Service Reference', but that option is not offered for devices projects. We can use the netcfsvcutil tool that's in the Power Toys to generate a client-side proxy, but there's actually an easier way. Since the WCF basicHttpBinding is actually the same thing as an 'old-fashioned' Web Service, we can just Add Web Reference to generate ourselves a client-side proxy for this service. This isn't quite as easy as it sounds, because first we must get the service running so that the Add Web Reference tool can query it to interrogate the WSDL and generate the proxy.

  1. To get the service running, right-click on the WCF Service Library project and then click Set as Startup Project. Now click the Debug menu, option Start without Debugging (or CTRL-F5 if you're using C# developer keymappings in Visual Studio). The solution will build, and then it will put up the Deploy SmartDeviceSyncClient window. Click Cancel here, because we don't want to deploy the client-side project yet. You'll then see the There were deployment errors. Continue? pop-up, but here click Yes because we want to continue to run the WCF Service.
  2. If all is well, you'll then see the WCF Test Client window displaying - the red exclamation marks against the methods simply indicate that the datatypes they expose are too complex to allow you to use the WCF Test Client to exercise them.

  3. Leaving the WCF Test Client running, now go to Solution Explorer, right-click the devices project and then click Add Web Reference. In the Add Web Reference wizard, enter the URL where the WCF Service is running - be careful to append a trailing '/' !! If you leave that off, it won't find it!
    Change the Web Reference name to something meaningful such as NorthwindProxy, then click Add Reference.

  4. This should generate you the proxy class for the service, and if you compile it, no compile errors will be reported. However, there is still a problem with the proxy that has been generated, and if you try to run this solution (later on when we've added a bit more code to the client), then you'll get runtime errors as you'll find that certain classes used for Sync Services are multiply defined. That's because the methods exposed by the WCF Service use a number of classes defined in the Sync Services namespaces, so the Add Web Reference wizard has looked at the WSDL for the service and generated versions of those classes in the proxy - but those classes are also defined in the Sync Services assemblies that are already referenced by the client project.
    To fix this, simply edit the proxy code. To find the code, click the Show All Fles icon at the top of Solution Explorer, and expand the Web References node in the client project, and drill down until you see the Reference.cs/.vb file. Open this up in the editor, and delete every class apart from the first, NorthwindCacheSyncService. Finally add using Microsoft.Synchronization.Data; (Imports... of course, for VB) to the top to fix up the now-missing class definitions and we're good to go. Remember though that if you ever refresh the Web Reference, you'll have to come in and make this edit again.

Coding the client

All that remains is to create a UI, add some code to sync the data and then run it.

  1. To create the UI, first build the solution. Then, show Form1 in the Designer. Double-click the Form background to create the Form1_Load event handler. Inside this, just add a call to a new method called Sync():

            private void Form1_Load(object sender, EventArgs e)
            {
                Sync();
            }

            private void Sync()
            {
                
            }
  2. Next on the Data menu, click Show Data Sources and drag the Customers table onto the Form (Design view), which will create a DataGrid for you. Set the Dock property to Fill to cover the entire form.
  3. Now let's complete the Sync method, where we need to write the code to synchronize the local cache database with the backend database.
    The Sync Designer helps here: open the designer again by double-clicking on NorthwindCache.sync in the WCF project, and click the Show Code Example link shown bottom right just above the OK-Cancel buttons. It now displays you the code you need on the client side, and also allows you to copy the code to the clipboard. Do this, and paste the code into the Sync method:

            private void Sync()
            {
                // Call SyncAgent.Synchronize() to initiate the synchronization process.
                // Synchronization only updates the local database, not your project's data source.
                NorthwindCacheSyncAgent syncAgent = new NorthwindCacheSyncAgent();
                Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();

                // TODO: Reload your project data source from the local database (for example, call the TableAdapter.Fill method).

            }
  4. Now this code is fine for a two-tier solution, but for a 3-tier solution where synchronization takes place via a WCF or Web Service, some additional code is required. First you need to create an instance of your service proxy, then create an instance of Microsoft.Synchronization.Data.ServerSyncProviderProxy, the constructor of which requires the service proxy object, and then you set the RemoteProvider property of the SyncAgent object to that ServerSyncProviderProxy instance before calling Synchronize.
    If we add some code to refresh the DataSet object with the data from the database after it has been synchronized, and a MessageBox just to report what has happened during sync, the end result looks like this:

            private void Sync()
            {
                Cursor.Current = Cursors.WaitCursor;
                NorthwindProxy.NorthwindCacheSyncService svcProxy = new NorthwindProxy.NorthwindCacheSyncService();
                Microsoft.Synchronization.Data.ServerSyncProviderProxy syncProxy =
                    new Microsoft.Synchronization.Data.ServerSyncProviderProxy(svcProxy);

                // Call SyncAgent.Synchronize() to initiate the synchronization process.
                // Synchronization only updates the local database, not your project's data source.
                NorthwindCacheSyncAgent syncAgent = new NorthwindCacheSyncAgent();
                syncAgent.RemoteProvider = syncProxy;
                Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();

                // TODO: Reload your project data source from the local database (for example, call the TableAdapter.Fill method).
                customersTableAdapter.Fill(northwindDataSet.Customers);

                // Show synchronization statistics
                MessageBox.Show("Changes downloaded: " + syncStats.TotalChangesDownloaded.ToString()
                    + "\r\nChanges Uploaded: " + syncStats.TotalChangesUploaded.ToString());

                Cursor.Current = Cursors.Default;
            }
  5.  To finish up, click the SmartTag top right of the DataGrid in Design View, then click Generate Data Forms which will generate some UI to allow you to edit records on the client-side.
    Make the following code addition to the customersDataGrid_Click method to ensure that changes you make during editing are written back to the underlying database:

            private void customersDataGrid_Click(object sender, EventArgs e)
            {
                SmartDeviceSyncClient.CustomersSummaryViewDialog customersSummaryViewDialog = SmartDeviceSyncClient.CustomersSummaryViewDialog.Instance(this.customersBindingSource);
                customersSummaryViewDialog.ShowDialog();
                // Update the database with changes made to the dataset
                customersTableAdapter.Update(northwindDataSet.Customers);
            }
  6. Now create a menu item on the right menu key of Form1 with the text Sync, and in the click event handler just call the Sync() method we created earlier.
  7. Finally, we have to set the sync up for bidirectional synchronization, as it is configured for download only by default. You program this change in the client project.
    In Solution Explorer, find NorthwindCache.Client.sync, right click it and click View Code. Add the following line to the OnInitialized method:

            partial void OnInitialized(){
                Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional;
            }
  8. That's it! Make sure you set the client project as the Startup project, then debug this on a device that is cradled, or connected by WiFi to the network where your server is located and it will first sync when the Form loads. It will report no changes uploaded or downloaded first time, because the project was setup with a pre-synchronized database. But if you now click on a row in the datagrid, and change the contents of one of the fields in the Edit form, then click the Sync menu, you'll see that the changes are synced back to the server. Change some data on the server side (an easy way of doing this is to click on the Customers table in Server Explorer in Visual Studio, then click Show Table Data to open up a Grid view of the data on the server side), then sync the client again, and the changes get sent up to the device.

 

 


Posted Dec 08 2008, 11:30 AM by Andy Wigley
Copyright © 2014 APPA Mundi Limited. All Rights Reserved. Terms of Use and Privacy Policy. OrcsWeb's Windows Cloud Server Hosting