Chapter 28. Background File Transfers


In This Chapter

Downloading and uploading files asynchronously in the background

Handling app termination and background request event resubscription

Using URL routing to pass information to the server during a background transfer request

Retrieving the user’s Windows Live anonymous ID

Monitoring a background transfer request using a progress indicator


When developing a phone app, your app may need to transfer files to or from a remote server. In some cases, your app may not be able to continue without having a certain file on hand. But in other cases, the file may not be required immediately and can be downloaded asynchronously using a background file transfer. Candidates for these kinds of file transfers include downloading large files (for example, music and video files) or backing up an app’s local database.

Transferring files from a foreground app can be problematic due to the transient nature of an app’s run cycle; if your app is tombstoned or terminated, any file transfers under way are interrupted. Background file transfers allow your app to download or upload files asynchronously and remain active even if your app is terminated, while still allowing your app to monitor the progress of the transfer.

The previous chapter looked at background actions and how a todo list app uses a local database to store todo item information. This chapter continues with the todo list sample, and you see how to back up and restore the todo list local database using a background transfer request. The chapter examines how to leverage the URL routing features of ASP.NET to pass arguments to a WCF service via a background transfer request, and you see how to retrieve the user’s Windows Live anonymous ID. Finally the chapter looks at monitoring a background transfer using a progress indicator.

Background Transfer Requests

Transfer requests are represented by the BackgroundTransferRequest class. The BackgroundTransferService maintains a queue of background requests and is used to submit new background requests, remove requests from the queue, and retrieve active requests.

Submitting a background request can be done by creating an instance of the BackgroundTransferRequest class, specifying the request end points, and then adding the request to the BackgroundTransferService. The following excerpt shows a transfer request that downloads a file from a remote server to a location in isolated storage:

BackgroundTransferRequest request
    = new BackgroundTransferRequest(remoteUri, localUri)
    {
        TransferPreferences = TransferPreferences.AllowBattery,
        Method = "Get",
    };

BackgroundTransferService.Add(request);


Note

An app can have at most five requests queued at a given time. Attempting to add more than five raises an InvalidOperationException. It is the responsibility of the app to remove requests from the queue by using the BackgroundTransferService.Remove method after requests have completed.


By default, background transfer requests occur only when the device has a Wi-Fi connection and is connected to external power. By using the TransferPreferences property of the BackgroundTransferRequest class, you can override this behavior so that transfers occur when one or both of these conditions are not met. Table 28.1 describes the available TransferPreferences values.

Table 28.1. TransferPreferences Enum

Image

Note

Setting the TransferPreferences property does not guarantee that transfers will occur under the preferred conditions. If the battery level is critical, for example, background transfers are suspended regardless of the TransferPreferences value.



Caution

Transferring files when the phone does not have a Wi-Fi connection can unduly consume the user’s data plan. In addition, transferring files without an external power connection can rapidly drain the phone’s battery. Both cases can potentially result in a poor experience for the user, and it is therefore recommended to leave the default value of TransferPresences as None.


BackgroundTransferRequest is bidirectional, allowing you to transfer files from and to the phone device. The BackgroundTransferRequest API is peculiar in that it determines the direction of the request based on which of its two constructors is used and whether its UploadLocation property has been set. The constructor to use when performing a download (from a remote server to isolated storage) has two parameters: the remote URI and the local (destination) URI. To perform an upload (from isolated storage to a remote server), its single parameter constructor is used in conjunction with its UploadLocation property. The following excerpt demonstrates the creation of an upload transfer request:

Uri remoteUri = new Uri(remoteUrl, UriKind.Absolute);

BackgroundTransferRequest request
    = new BackgroundTransferRequest(remoteUri)
            {
                Method = "POST",
                UploadLocation = new Uri(uploadPath, UriKind.Relative)
            };

BackgroundTransferService.Add(request);

By default, BackgroundTransferRequest uses the HTTP method GET.


Note

If you are performing an upload and fail to set the method to POST, an ArgumentNullException is raised because the BackgroundTransferRequest mistakes your request for a download and complains that the download URI was not supplied. Conversely, if Method is set to anything other than GET, BackgroundTransferRequest assumes the transfer is an upload and raises an ArgumentNullException if UploadLocation is not specified.

It is another peculiar aspect of the API that an ArgumentNullException is raised rather than an InvalidOperationException.


A parameter indicating the direction and nature of the transfer would, in my view, make the BackgroundTransferRequest easier to use.

BackgroundTransferRequest provides the following two events for monitoring the progress and status of a background transfer:

TransferProgressChanged

TransferStatusChanged

Both events pass a BackgroundTransferEventArgs object that provides nothing apart from a reference to the associated BackgroundTransferRequest.

The TransferProgressChanged event, as its name implies, allows you to track the amount of data that has been transferred. The following excerpt shows a TransferProgressChanged event handler that determines the current progress of a transfer as a value between 0 and 1:

void HandleTransferProgressChanged(object sender,
                                   BackgroundTransferEventArgs e)
{
    if (e.Request.BytesSent > 0)
    {
        Progress = (double)e.Request.TotalBytesToSend / e.Request.BytesSent;
    }
    else
    {
        Progress = 0;
    }
}

The TransferStatusChanged event allows you to monitor the critical events of the transfer request and enables you to determine, for example, when the transfer has completed and whether an error has occurred, as shown:

void HandleTransferStatusChanged(object sender,
                                 BackgroundTransferEventArgs e)
{
    if (e.Request.TransferStatus == TransferStatus.Completed)
    {
        BackgroundTransferService.Remove(e.Request);

        if (e.Request.TransferError != null)
        {
            /* Handle the error. */
            return;
        }
    }
}

The Request.TransferStatus property identifies the request’s current state. Table 28.2 describes each TransferStatus enum value.

Table 28.2. TransferStatus Enumeration

Image
Image

The role of the TransferStatusChanged event is discussed in greater detail later in the chapter.

Handling App Termination and Resubscription to Transfer Events

When a BackgroundTransferRequest has been added to the BackgroundTransferService, you can store the value of the request’s RequestId property in isolated storage. This enables you to retrieve the request later if your app is terminated and to continue monitoring the progress of your background request by resubscribing to the request’s TransferStatusChanged and TransferProgressChanged events.

The BackgroundTransferService.Find method is used to retrieve an existing request, like so:

BackgroundTransferRequest request
        = BackgroundTransferService.Find("request id string");
if (request != null)
{
    request.TransferStatusChanged += HandleUploadTransferStatusChanged;
    request.TransferProgressChanged += HandleTransferProgressChanged;
}

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).

Image

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.

Summary

In this chapter you saw how to back up and restore the todo list database using a background transfer request. The chapter examined how to leverage the URL routing features of ASP.NET to pass arguments to a WCF service via a background transfer request, and you saw how to retrieve the user’s Windows Live anonymous id. Finally, the chapter looked at monitoring a background transfer using a progress indicator.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.98.111