Free Trial

Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.

Share this Page URL
Help

Chapter 28. Background File Transfers > Background File Transfer Sample Code

Feedback: 0 Comments on this Section

Background File Transfer Sample Code

Background File Transfer Sample Code The sample for this chapter continues from where we left off in Chapter 27, “Scheduled Actions.” This chapter looks at backing up the todo items database to a remote server using WCF. Within the BackgroundAgents solution in the downloadable sample code is a project named WindowsPhone7Unleashed.BackgroundAgents.Web. This project exposes a WCF service named BackupService, which allows the phone app to save files to the Backups directory on the server by way of its SaveFile method (see Listing 28.1). The SaveFile method accepts a Stream, which is written to a file on the server. The unique id of the user is also sent to the server to allow correct retrieval of the file at a later time. Listing 28.1. BackupService Class [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class BackupService : IBackupService
{
    public void SaveFile(string userId, string fileName, Stream fileStream)
    {
        string location = string.Format(
                             @"~\Backups\{0}_{1}", userId, fileName);
        var filepath = HttpContext.Current.Server.MapPath(location);
        using (Stream outputStream = File.OpenWrite(filepath))
        {
            CopyStream(fileStream, outputStream);
        }
    }

    static void CopyStream(Stream input, Stream output)
    {
        var buffer = new byte[8 * 1024];
        int streamLength;

        while ((streamLength = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            output.Write(buffer, 0, streamLength);
        }
    }
} There is no retrieval method in the WCF service because an ordinary HTTP GET request is used to download the file. The WindowsPhone7Unleashed.BackgroundAgents contains a web reference to the WindowsPhone7Unleashed.BackgroundAgents.Web project, and the service is consumed within the TodoListViewModel class. Using URL Rerouting with a WCF Service The Web Application project uses URL rerouting to allow the BackgroundTransferRequest to pass the user ID and filename to the service via the URL. The routing system on the server is initialized in the RegisterRoutes method of the Global class in the Web Application project. The URL routing APIs reside in the System.Web.Routing namespace. A new ServiceRoute is added to the RouteTable, so that when a request for the URL BackupService arrives, the built-in WebServiceHostFactory creates an instance of the BackupService class to service the request. See the following excerpt: public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RegisterRoutes();
    }

    void RegisterRoutes()
    {
        RouteTable.Routes.Add(new ServiceRoute(
            "BackupService", new WebServiceHostFactory(),
             typeof(BackupService)));
    }
...
} The service interface for the backup service defines a UriTemplate that maps the incoming request URL, which includes the user ID and filename, to the SaveFile method parameters. This means that we are able to translate the incoming URL and forward the call to the service method. See the following: [ServiceContract(Namespace = "http://danielvaughan.org")]
public interface IBackupService
{
    [OperationContract, WebInvoke(
        Method = "POST", UriTemplate = "UploadFile/{userId}/{fileName}")]
    void SaveFile(string userId, string fileName, Stream fileStream);
} URL rerouting is an elegant way of providing a WCF service with extra information, such as the user’s ID, while still remaining compatible with the BackgroundTransferRequest and its simple Uri UploadLocation property. Retrieving the User’s Windows Live Anonymous ID To associate a database file on the server with the user of the device, the app retrieves the user’s Windows Live anonymous ID from the Microsoft.Phone.Info.UserExtendedProperties class. The Windows Live anonymous ID is a representation of the user’s Windows Live ID that does not include any user identifiable information. When a user activates a device, he must provide a Windows Live ID. The Windows Live anonymous ID lets you identify the user by his Windows Live account, without actually seeing his details, and there is no way to correlate the anonymous ID with the Windows Live ID. When retrieving the anonymous ID from UserExtendedProperties by way of the ANID key value (see Listing 28.2), the resulting string contains a series of name value pairs resembling a URL query string, like the following: A=1E234A328BC18118DB64D915FFFFFFFF&E=a45&W=1 The anonymous ID is a GUID consisting of 32 characters. It is extracted by skipping the first two characters of the value, as these characters are the name part of a name=value pair. Listing 28.2. DeviceProperties Class public class DeviceProperties : IDeviceProperties
{
    /// <summary>
    /// Gets the windows live anonymous id.
    /// This method requires ID_CAP_IDENTITY_USER
    //  to be present in the capabilities of the WMAppManifest.
    /// </summary>
    /// <returns>The string id for the user.</returns>
    static string GetWindowsLiveAnonymousId()
    {
        const int idLength = 32;
        const int idOffset = 2;

        string result = string.Empty;
        object id;
        if (UserExtendedProperties.TryGetValue("ANID", out id))
        {
            string idString = id != null ? id.ToString() : null;
            if (idString != null && idString.Length >= (idLength + idOffset))
            {
                result = idString.Substring(idOffset, idLength);
            }
        }

        return result;
    }

    string windowsLiveAnonymousId;

    public string WindowsLiveAnonymousId
    {
        get
        {
            return windowsLiveAnonymousId
                    ?? (windowsLiveAnonymousId = GetWindowsLiveAnonymousId());
        }
    }
} Tip It is better to associate data with the user of the device rather than the device itself. By relying on the ID of the user, rather than the ID of the device, you can provide a greater level of assurance that if the phone changes ownership, the new user of the phone will not have access to previous owner’s data. Retrieving the anonymous ID from the UserExtendedProperties does not work on the emulator. For testing purposes, the IDeviceProperties implementation can be swapped with a mock implementation that retrieves a predefined anonymous ID. TodoListViewModel The TodoListViewModel constructor accepts an IDeviceProperties instance and an ITodoService instance, which, as you saw in Chapter 27, is used for storage and retrieval of todo items. See the following excerpt: public TodoListViewModel(
    ITodoService todoService, IDeviceProperties deviceProperties)
{
...
    backupDatabaseCommand = new DelegateCommand(obj => BackupDatabase());
    restoreDatabaseCommand = new DelegateCommand(obj => RestoreDatabase());

    Load();
} The viewmodel contains a method that leverages the IDeviceProperties instance to create a unique ID to use to identify itself to calls to a WCF service. string GetUserId()
{
    string id = deviceProperties.WindowsLiveAnonymousId;
    if (string.IsNullOrWhiteSpace(id))
    {
        id = "Emulator";
    }
    return id;
} The anonymous ID is passed to the WCF service when backing up the local database file and used as part of the URL when restoring it. Backing Up the Local Database To transfer the local database to the server, it must first be copied to a directory in isolated storage. This prevents the file from being modified by the SQL CE engine while the transfer is under way. The viewmodel’s BackupDatabase method creates a temporary directory and then copies the local .sdf database file to the directory, as shown: string uploadUrl = "http://localhost:60182/BackupService/UploadFile/";
const string localDatabaseName = "Todo.sdf";
const string transferDirectory = "/shared/transfers";

string uploadPath = transferDirectory + "/" + localDatabaseName;

using (IsolatedStorageFile isolatedStorageFile
            = IsolatedStorageFile.GetUserStoreForApplication())
{
    if (!isolatedStorageFile.FileExists(localDatabaseName))
    {
        throw new InvalidOperationException(
            "Database file does not exist in isolated storage.");
    }

    if (!isolatedStorageFile.DirectoryExists(transferDirectory))
    {
        isolatedStorageFile.CreateDirectory(transferDirectory);
    }

    isolatedStorageFile.CopyFile(localDatabaseName, uploadPath, true);
} The BackupDatabase method then constructs a destination URL for the upload. The remote URL is constructed using the base URL of the upload file path on the server. This URL is rerouted when it arrives at the server, and its segments are passed as arguments to the SaveFile WCF service method. See the following excerpt: string deviceId = GetUserId();

string remoteUrl = string.Format("{0}{1}/{2}",
                                    uploadUrl,
                                    deviceId,
                                    localDatabaseName);

Uri remoteUri = new Uri(remoteUrl, UriKind.Absolute); A BackgroundTransferRequest is constructed, which causes the file to be uploaded to the server. Uploads use the HTTP POST method, as shown: BackgroundTransferRequest request
    = new BackgroundTransferRequest(remoteUri)
            {
                TransferPreferences = TransferPreferences.AllowBattery,
                Method = "POST",
                UploadLocation = new Uri(uploadPath, UriKind.Relative)
            }; To monitor the progress of the transfer request, while the app is running in the foreground, we subscribe to the TransferStatusChanged and the TransferProgressChanged events. The transfer request is then added to the BackgroundTransferService, which queues the upload. See the following: request.TransferStatusChanged += HandleUploadTransferStatusChanged;
request.TransferProgressChanged += HandleUploadTransferProgressChanged;

BackgroundTransferService.Add(request);
Message = "Backing up data to cloud.";
ProgressVisible = true; When the viewmodel’s ProgressVisible property is set to true, it causes a progress indicator to be displayed in the view. This occurs via a custom ProgressIndicatorProxy class, first discussed in Chapter 5, “Content Controls, Items Controls, and Range Controls.” In addition, the progress indicator also displays the progress of the operation via the viewmodel’s Progress property. The Progress property is updated whenever the TransferProgressChanged event is raised, as shown: void HandleTransferProgressChanged(
        object sender, BackgroundTransferEventArgs e)
{
    if (e.Request.BytesSent > 0)
    {
        Progress = (double)e.Request.TotalBytesToSend / e.Request.BytesSent;
    }
    else
    {
        Progress = 0;
    }
} When the request’s TransferStatusChanged event is raised, if the request has completed, it is removed from the BackgroundTransferStatus, and the progress indicator is hidden. Note A TransferStatus of Completed does not necessarily mean that the transfer completed successfully, but rather that the operation has ended for whatever reason. It is therefore critical to test for the presence of an error contained in the Request.TransferError property. The ViewModelBase class’s MessageService is used to display the result to the user, as shown: void HandleUploadTransferStatusChanged(
        object sender, BackgroundTransferEventArgs e)
{
    if (e.Request.TransferStatus == TransferStatus.Completed)
    {
        BackgroundTransferService.Remove(e.Request);
        ProgressVisible = false;

        if (e.Request.TransferError != null)
        {
            MessageService.ShowError("An error occured during backup.");
        }
        else
        {
            MessageService.ShowMessage("Backup successful.");
        }
    }
} Once the local database file has been transferred to the server, the user can nominate to restore the database from the backup. Restoring the Local Database Restoring the local database involves submitting a background transfer request to download the previously uploaded file from the server. The file is downloaded to a temporary location in isolated storage, the existing local database is disconnected, and its file is replaced. The RestoreDatabase method begins by creating a temporary directory where the downloaded .sdf file can be placed by the BackgroundTransferService: const string downloadPath = transferDirectory + "/" + localDatabaseName;

using (IsolatedStorageFile isolatedStorageFile
            = IsolatedStorageFile.GetUserStoreForApplication())
{
    if (!isolatedStorageFile.DirectoryExists(transferDirectory))
    {
        isolatedStorageFile.CreateDirectory(transferDirectory);
    }
} It then creates two Uri objects specifying the location of the .sdf file on the remote server, and the file’s destination location in isolated storage, as shown: string deviceId = GetUserId();

string remoteUrl = string.Format("{0}{1}_{2}",
                                    downloadUrl,
                                    deviceId,
                                    localDatabaseName);
Uri remoteUri = new Uri(remoteUrl, UriKind.Absolute);
Uri localUri = new Uri(downloadPath, UriKind.Relative); The BackgroundTransferRequest is constructed using the two Uri objects. The default HTTP method Get is used because we are downloading the file to the device. See the following excerpt: BackgroundTransferRequest request
    = new BackgroundTransferRequest(remoteUri, localUri)
    {
        TransferPreferences = TransferPreferences.AllowBattery,
    }; Finally, we subscribe to the transfer request’s status changed and progress changed events, and the request is added to the BackgroundTransferService. The progress indicator is displayed, and it is updated as the progress of the background transfer changes: request.TransferStatusChanged += HandleDownloadTransferStatusChanged;
request.TransferProgressChanged += HandleDownloadTransferProgressChanged;

BackgroundTransferService.Add(request);
Message = "Restoring data from cloud.";
Progress = 0;
ProgressVisible = true; When the background transfer completes the TransferStatusChanged event handler is called (see Listing 28.3). The downloaded file is copied to the location of the local database, which replaces the existing file. The ITodoService.Initialize method re-creates the connection to the database, and the viewmodel’s GroupedTodoItems are re-created via a call to PopulateItems. Listing 28.3. HandleDownloadTransferStatusChanged Method void HandleDownloadTransferStatusChanged(object sender,
                                         BackgroundTransferEventArgs e)
{
    if (e.Request.TransferStatus == TransferStatus.Completed)
    {
        BackgroundTransferService.Remove(e.Request);

        ProgressVisible = false;

        if (e.Request.TransferError != null)
        {
            MessageService.ShowError("An error occured during restore.");
            return;
        }
        try
        {
            using (IsolatedStorageFile isolatedStorageFile
                         = IsolatedStorageFile.GetUserStoreForApplication())
            {
                string downloadedFile
                          = e.Request.DownloadLocation.OriginalString;
                isolatedStorageFile.CopyFile(downloadedFile,
                                             localDatabaseName, true);
            }

            todoService.Initialize();

            ClearPinnedItems();

            PopulateItems();
        }
        catch (Exception ex)
        {
            MessageService.ShowError("An error occured during restore.");
            return;
        }

        MessageService.ShowMessage("Restore successful.");
    }
} Backup and restore operations are actuated by application bar menu items in the view. The view’s AppBar menu items are bound to the BackupDatabaseCommand and the RestoreDatabaseCommand (see Figure 28.1). Figure 28.1. The TodoListView provides menu items for backing up and restoring the local database, and a progress indicator that shows the progress of the background transfer request. Using a BackgroundTransferRequest is an effective way to back up your app’s data because it does not rely on your foreground app being active. Be mindful, however, that the BackgroundTransferService does not guarantee that a transfer request will be serviced.

You are currently reading a free preview of this book.

                                                                                                         

Sign up for a
Safari Library subscription
to access Rough Cuts.

  

Subscribe Now