One of the more complex tasks in building offline-enabled (aka occasionally connected/disconnected) software is how you handle synchronization. Most synchronization frameworks typically handle synchronization of one form of data. For example Mobile Services allow for synchronization of individual tables based on a query. However, for most application this isn’t sufficient – they may require database synchronization in addition to uploading of new images taken on the device and downloading any associated documents for offline viewing. This means you need a synchronization layer that can co-ordinate synchronization of different data types/formats/processes.
There may be times within the application where you don’t want to perform a full synchronization. For example if the user creates a new record, the application should attempt to push this new record to the server immediately but it may not want to do a full synchronization until the user hits the sync button.This means you need a mechanism where you can partition the synchronization layer and only trigger synchronization of individual parts as required.
Here’s an example of what the synchronization wrapper might look like in action:
[Flags]
public enum SyncStages
{
None=0,
Stage1=1,
Stage2=2,
Stage3=4,
All = Stage1 | Stage2 | Stage3
}public class TestSynchronization {
public SynchronizationContext<SyncStages> SynchronizationManager { get; set; }public Action<string> Progress { get; set; }
public TestSynchronization()
{
SynchronizationManager = new SynchronizationContext<SyncStages>();
SynchronizationManager.DefineSynchronizationStep(SyncStages.Stage1, Step1);
SynchronizationManager.DefineSynchronizationStep(SyncStages.Stage2, Step2);
SynchronizationManager.DefineSynchronizationStep(SyncStages.Stage3, Step3);
SynchronizationManager.SynchronizationChanged += SynchronizationManager_SynchronizationProgressChanged;
}private void SynchronizationManager_SynchronizationProgressChanged(object sender, SynchronizationEventArgs<SyncStages> e)
{
var message = e.ToString();
Progress(message);
}private const int Step1Stages = 5;
public async Task<bool> Step1(ISynchronizationStage<SyncStages> step)
{
step.RegisterSubStagesCount(Step1Stages);
for (int i = 0; i < Step1Stages; i++)
{
step.StartSubStage();
await Task.Delay(1000, step.CancellationToken);
step.EndSubStage();
if (step.CancellationToken.IsCancellationRequested) return false;
}
return true;
}public async Task<bool> Step2(ISynchronizationStage<SyncStages> step)
{
await Task.Delay(2*1000, step.CancellationToken);
return true;
}public async Task<bool> Step3(ISynchronizationStage<SyncStages> step)
{
step.RegisterSubStages(Step3Stages.S3Stage1, Step3Stages.S3Stage2, Step3Stages.S3Stage3);await step.RunSubStage(Step3Stages.S3Stage1, Step3Sub);
await step.RunSubStage(Step3Stages.S3Stage2, Step3Sub);
await model.SynchronizationManager.Synchronize(SyncStages.Stage1 | SyncStages.Stage2,
waitForSynchronizationToComplete: true);
}private async void FullSynchronizeClick(object sender, RoutedEventArgs e)
{
await model.SynchronizationManager.Synchronize(SyncStages.All,
cancelExistingSynchronization:true,
waitForSynchronizationToComplete: true);return true;
}private enum Step3Stages
{
S3Stage1,
S3Stage2,
S3Stage3
}private async Task<bool> Step3Sub(ISynchronizationStage<Step3Stages> step)
{
step.Progress(0.2);
await Task.Delay(2000);
step.Progress(0.7);
await Task.Delay(2000);
step.Progress(1.0);
return true;
}}
Triggering and/or cancelling synchronization can then be done using the following:
await model.SynchronizationManager.Synchronize(SyncStages.Stage1 | SyncStages.Stage2, waitForSynchronizationToComplete: true);
await model.SynchronizationManager.Synchronize(SyncStages.All,
cancelExistingSynchronization:true,
waitForSynchronizationToComplete: true);
await model.SynchronizationManager.Cancel(true);
The first line triggers stages 1 and 2 to be synchronized – it won’t cancel any existing synchronization process and will only return once it has completed the synchronization process; The second line triggers all stages to be synchronized and will cancel any existing synchronization process; The third line will cancel any existing synchronization and only return once they’ve been cancelled.
I’ve attached a first pass at an implementation of such a sync framework. Note that the actual sync logic is in the steps shown above, it’s the framework for scheduling them and reporting progress which is being shown in the sample.