Freigeben über


Creating LINQ Data provider for WP7 (part 2)

In my previous post I described how to create a simple LINQ data provider for WP7. Today I am going to show you how we can make use of this library as well as Push Notifications functionality that is available for WP7 to create a custom data sync sample application. Let's come up with the requirements for this app first:

- The application will consist of 2 master/detail pages displaying customers from the Northwind database.

- Retreive the data from a web service when an application starts and persist this data into the isolated storage.

- Populate the master page with the data from the isolated storage.

- Whenever the data gets updated on the server, update the UI with the uptodate data.

Here's the architecture that I came up with:

Let's take care of the server side of the application first. First of all we need to have full control over the Northwind database, so I have installed it on my developer machine and then created an ASP.NET application with the Northwind Data Service. Since we already decided that we are going to use the Push Notification services as a mechanism to notify the phone app of the changed data, let's create appropriate infrastructure. As you know (if not please read this and come back when you are done), when a WP7 device registers for receiving notifications it gets a url of the persisting notification channel which could be used by an external application to send notification to. As a result we need to be able to store the channel urls and have an application that would retreive these urls and send notification to a device. For storing the urls I have created a SQL Server database with a single table:

CREATE TABLE [dbo].[Channel](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Url] [nvarchar](400) NULL,
[Status] [bit] NULL,
[DateCreated] [datetime] NULL,
CONSTRAINT [PK_Channel] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF,

STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

 Next, I've created a web service with the following method:

  public void Register(string channelUri)
 {
        NotificationsRegistryEntities registry = new NotificationsRegistryEntities();
        if (registry.Channel.Where(c => c.Url == channelUri).Count() == 0)
        {
              Channel channel = registry.CreateObject<Channel>();
              channel.Status = true;
              channel.Url = channelUri;
              channel.DateCreated = DateTime.Now;
 
              registry.Channel.AddObject(channel);
              registry.SaveChanges();
        }
  }

As you can see this method accepts a channel url as a parameter and inserts it into the Channel table.

In the real life scenario you would have some service that monitors if data has been changed in the Northwind database and send the notification to a device. But for the purpose of the sample, I've created a simple web page that lists the records in the Channel table and allows to send notification to the selected channel in the list:

The code that is executed when the send button is clicked just sends raw notification to the selected channel:

         private void SendRawNotification(string url, string payload)
        {
            HttpWebRequest sendNotificationRequest = (HttpWebRequest)WebRequest.Create(url);
 
            // HTTP POST is the only allowed method to send the notification.
            sendNotificationRequest.Method = "POST";
            sendNotificationRequest.Method = WebRequestMethods.Http.Post;
            sendNotificationRequest.ContentType = "text/xml; charset=utf-8";
 
            sendNotificationRequest.Headers.Add("X-NotificationClass", "3");
            sendNotificationRequest.Headers.Add("X-MessageID", Guid.NewGuid().ToString());
 
            // Sets the notification payload to send.            
            byte[] notificationMessage = new UTF8Encoding().GetBytes(payload);
 
            // Sets the web request content length.
            sendNotificationRequest.ContentLength = notificationMessage.Length;  
 
            using (Stream requestStream = sendNotificationRequest.GetRequestStream())
            {
                requestStream.Write(notificationMessage, 0, notificationMessage.Length);
            }
 
            string status = "";
 
            try
            {
                HttpWebResponse response = (HttpWebResponse)sendNotificationRequest.GetResponse();
                string notificationStatus = response.Headers["X-NotificationStatus"];           //(Received|Dropped|QueueFull|)
                string deviceConnectionStatus = response.Headers["X-DeviceConnectionStatus"];   //(Connected|InActive|Disconnected|TempDisconnected)
                string notificationChannelStatus = response.Headers["X-SubscriptionStatus"];
 
                status = String.Format("Status: {0}|{1}|{2}", notificationStatus, deviceConnectionStatus, notificationChannelStatus);
            }
            catch (Exception ex)
            {
                status = "Status: Notification Failed.";
            }
 
            labelStatus.Text = status;
 
        }

Now we should be ready to move to the device side. Let's start from the NotificationManager class. This class's responsibility is to open the notification channel, receive the channel url and make a call to the Register method of the Notifications web service. The NotificationManager class is also responsible for raising the OnStatusChanged event whent he notification is received.  Let's can move to sync providers. I've created the following ISyncProvider interface:

     public interface ISyncProvider
    {
        void GetChanges();
        void SaveChanges(IEnumerable source);
        event EventHandler SyncCompleted;
       
    }

This interface is implemented by two classes: NorthwindServiceProvider and NorthwindLocalStoreProvider. The NorthwindLocalStoreProvider class utilizes the LINQ data provider to save the changes to isolated storage. This is how the SaveChanged method looks like:

         public void SaveChanges(System.Collections.IEnumerable source)
        {
            this.objectStore.Persist<Customer>((IEnumerable<Customer>)source);
            // Notify SyncManager
            if (this.SyncCompleted != null)
            {
                this.SyncCompleted(this, null);
            }
        }

The NorthwindServiceProvider uses the NorthwindEntities class to communicate with the Northwind Data Service to retreive customer records from the server. The SyncAgent class instantiates both providers hooks up into the events from both and implements methods:

         public void SynchronizeRemote()
        {
            this.remoteProvider.GetChanges();
        }
 
        public void SynchronizeLocal(IEnumerable source)
        {
            this.localProvider.SaveChanges(source);
        }

 The SyncManager class ties all components together by instantiating NotificationManager and SyncAgent and raising events for the UI on the sync status. Here's a snippet of the SyncManager's constructor:

         public SyncManager(string channelName, SyncAgent syncAgent)
        {
            // Init notification manager
            this.notificationManager = new NotificationManager(channelName);
            this.notificationManager.StatusChanged += new EventHandler<NotificationStatusChangedEventArgs>(notificationManager_StatusChanged);
 
            // Init Sync manager
            this.syncAgent = syncAgent;
            this.syncAgent.RemoteSyncCompleted += new EventHandler(syncAgent_RemoteSyncCompleted);
            this.syncAgent.LocalSyncCompleted += new EventHandler(syncAgent_LocalSyncCompleted);
            // Start notification manager
            this.notificationManager.Start();
        }

Finally, the SyncManager is used in the MainPage to start the sync process:

         private void PrepareData()
        {
            // Prepare model and context
            this.context = new NorthwindContext();
            App.ViewModel = new CustomersViewModel(this.context);
            // Initialize SyncManager
            this.syncManager = new SyncManager("CustomerSync", new SyncAgent(new NorthwindLocalStoreProvider(), new NorthwindServiceProvider()));
            this.syncManager.SyncCompleted += new EventHandler(syncManager_SyncCompleted);
            this.syncManager.SyncStatusChanged += new EventHandler<SyncStatusChangedEventArgs>(syncManager_SyncStatusChanged);
            // Start sync
            this.syncManager.Synchronize();
            this.progressBar.IsIndeterminate = true;
        }

You are free to walk through the sample and investigate all other parts of code and how it works. You can download the sample from the MSDN code gallery:

https://code.msdn.microsoft.com/wp7linq

 

In this post I walked through the sample of a custom data sync for Windows Phone 7. This code was a part of the presentation that made at the Techready - internal Microsoft conference last week. 

Enjoy...

Comments

  • Anonymous
    August 03, 2010
    Are you working on a solution to persist locally modified data to the local storage? I don't mind right now to sync data back and forth up to the cloud - but that would be the next step too - sync phone-changed data to the central store - then you are near the stuff the sync framework is providing... If you persist the same collection again, you end up with an empty store after restart. I somehow need to persist a single entity too - not only collections of it :) Currently my project stores everything in XML serialized ObservableCollections - obviously that is only good if the data will ot be growing extremely... A file-based store would be much much better as the memory of the phone will have some limits :) Thx Markus