Backup your Windows Phone Isolated Storage data to SkyDrive using Live Connect API

September 15, 2011 33 comments

As a Windows Phone developer you know that you can store your application local data to Isolated Storage. Isolated Storage restricts all your I/O operations so other applications cannot access or corrupt your data, which provides very efficient security of app’s and user’s data. You can store WP7 local data to IsolatedStorageSettings, database or folders and files.

You also know that you have to backup your data in case of data loss or something else. Classic backup scenario includes simple data upload to your web application or cloud service. But if you tried to implement solution that includes upload to SkyDrive, DropBox or any other free cloud hosting service, you know that situation is not so simple, primarily because there are not any official API providers for Windows Phone platform. Off course, there are some community libraries that can help you, but in most cases they are not suitable for WP7 solutions or are very hard to implement. For example, there is community SkyDrive API library called SkyDrive .NET API Client, based on SkyDrive WebDAV web services, which works fine in web or desktop environment, but not for WP7. SkyDrive’s problem was lack of official Microsoft documentation and API. But few months ago Microsoft finally announced REST APIs for Windows Live platform. It was called “Messenger Connect”, but problem was that was primarily targeted to web client script developers, because APIs were exposed as JavaScript APIs. But no matter what, it was good sign. It was matter of time when we can expect managed (C#) API for Windows Live. On September 13th, Microsoft as a part of BUILD conference preliminary release of Windows 8, also published Live Connect, the new API that supports Metro style apps and Windows Phone apps. You can check out Live Connect now by visiting http://dev.live.com. Final release of Live Connect is expected in November 2011. You can download Live SDK Developer Preview from here.

 

Referencing the APIs with C#

When you download and unpack Live Connect SDK Developer Preview you will find Live Connect MSI setup file (LiveSDK.msi) and preliminary CHM documentation (LiveConnectPrelim.chm). When you install LiveSDK, you will find two new assemblies in Visual Studio .NET reference list: Microsoft.Live and Microsoft.Live.Controls:

image

Also, API ships with Live Connect Sign In control that will help you with user sign-in scenarios. If the control is not visible in your Toolbox you can register it by browsing Windows Phone\References\Microsoft.Live.Controls.dll assembly in Live SDK installation folder.

image

 

Getting a client ID and configuring your app

Before you continue with your development and call Live Connect APIs, you must configure your app with a unique identifier, which we call a client ID. You get a client ID and you can specify a redirect domain (if needed), at the Live Connect app management site. Sign in with your Microsoft account credentials, click Create application, type the app’s name and then click I accept. Live Connect creates and then displays the client ID. It should look something like this 00000000603E0BFE:

image

image

 

Sign-in user

To enable user to upload data to SkyDrive (or use any other Windows Live service like Hotmail, Contacts, Messenger or settings), the user must be signed in to Live Connect. A standard way to do this is by adding sign-in functionality to your app. This functionality (typically a button or a hyperlink) enables a user to provide their Microsoft account credentials. After the Live Connect APIs verify these credentials, it returns an access token to your app. This access token confirms that the user successfully signed in, and it specifies which parts of the user’s info your app can work with and for how long.

image

Best way to prompt a user to sign in in your WP7 app is to use the Live Connect sign-in control. You do this by setting the sign-in control’s properties in XAML code as is shown in code above. You must declare following namespace in your XAML page:

<phone:PhoneApplicationPage 
    …

xmlns:my="clr-namespace:Microsoft.Live.Controls;assembly=Microsoft.Live.Controls">

Also, you must set the Scopes, ClientId, RedirectUri and SessionChanged properties of Sign in control:

Scopes defines permissions from the user who owns the info to access his or her info or to create new objects on his or her behalf. In our scenario wl.skydrive_update scope is required to access and update user’s SkyDrive folders and files.

ClientId property is client ID of your Windows Live Application.

RedirectUri property defines landing page for you application. However, in Windows Phone scenarios that is not needed. In our case we will set RedirectUri in SignIn control to https://oauth.live.com/desktop.

SessionChanged property sets event handler method. Here is example how to set SessionChanged and LoginCompleted events in SignIn control that you put into your XAML page:

 

void btnSignin_SessionChanged(object sender, LiveConnectSessionChangedEventArgs e)
{
    if (e.Session != null &&
        e.Session.Status == LiveConnectSessionStatus.Connected)
    {
        client = new LiveConnectClient(e.Session);
        infoTextBlock.Text = "Signed in.";
        client.GetCompleted +=
            new EventHandler<LiveOperationCompletedEventArgs>(btnSignin_GetCompleted);
        client.GetAsync("me", null);
    }
    else
    {
        infoTextBlock.Text = "Not signed in.";
        client = null;
    }
}

void btnSignin_GetCompleted(object sender, LiveOperationCompletedEventArgs e)
{
    if (e.Error == null)
    {
        infoTextBlock.Text = "Hello, signed-in user!";
    }
    else
    {
        infoTextBlock.Text = "Error calling API: " +
            e.Error.ToString();
    }
}

For more information about user’s sign in, check “Signing users in” chapter in Live Connect documentation.

So, if you successfully set your XAML page and sign in code, following screenshots show user’s “sign in scenario”:

1) Click Sign in button:

image

 

2) User is redirected to Windows Live authentication page:

image

 

3) User is prompt to okay scopes that your application needs:

image

 

4) User is signed:

image

 

Upload data to SkyDrive

In this scenario I used Camera Capture feature of WP7 to capture camera image, save it to Isolated Storage and upload it to dedicated SkyDrive folder. CameraCaptureTask is used to capture desired image and save it to Isolated Storage. Following code describes capturing and image saving to Isolated Storage:

private LiveConnectClient client;
private CameraCaptureTask cameraCaptureTask;
private string cameraRollFileName = "MyCameraRollPicture.jpg";
private string skyDriveFolderName = "IsolatedStorageFolder";
private string skyDriveFolderID = string.Empty;

private void Camera_Click(object sender, EventArgs e)
{
    try
    {
        cameraCaptureTask.Show();
    }
    catch (System.InvalidOperationException ex)
    {}
}

private void cameraCaptureTask_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        BitmapImage bmp = new BitmapImage();
        Stream capturedImageStream = e.ChosenPhoto;
        bmp.SetSource(capturedImageStream);
        this.SaveImageToIS(bmp);
    }
}

private void SaveImageToIS(BitmapImage bitmapImage)
{

    Uri uri = new Uri(cameraRollFileName, UriKind.Relative);
    string file = uri.ToString();
    using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (store.FileExists(file))
            store.DeleteFile(file);
        IsolatedStorageFileStream fileStream = store.CreateFile(file);
        StreamResourceInfo sri = null;
        sri = Application.GetResourceStream(uri);
        WriteableBitmap wb = new WriteableBitmap(bitmapImage);
        Extensions.SaveJpeg(wb, fileStream, wb.PixelWidth, wb.PixelHeight, 0, 85);
        fileStream.Close();
    }
}

When user captures photo and clicks upload button, following scenario is happening:

1) Get info about user’s SkyDrive folders:

 

private void UploadPhoto_Click(object sender, EventArgs e)
{
   
    if (client == null || client.Session == null || client.Session.Status != LiveConnectSessionStatus.Connected)
    {
        MessageBox.Show("You must sign in first.");
    }
    else
    {
        client.GetCompleted += new EventHandler<LiveOperationCompletedEventArgs>(GetFolderProperties_Completed);
        // If you put photo to folder it becomes album.
        client.GetAsync("me/skydrive/files?filter=folders,albums");
    }
}

GetAsync is a helper function for making calls to the REST API. In our case we will get data about all folders and albums in user’s SkyDrive root folder.

 

2) Process data returned by GetCompleted handler:

private void GetFolderProperties_Completed(object sender, LiveOperationCompletedEventArgs e)
{

if (e.Error == null)
{
    Dictionary<string, object> folderData = (Dictionary<string, object>)e.Result;
    List<object> folders = (List<object>)folderData["data"];

    foreach (object item in folders)
    {
        Dictionary<string, object> folder = (Dictionary<string, object>)item;
        if (folder["name"].ToString() == skyDriveFolderName)
        skyDriveFolderID = folder["id"].ToString();
    }

    if (skyDriveFolderID == string.Empty)
    {
        Dictionary<string, object> skyDriveFolderData = new Dictionary<string, object>();
        skyDriveFolderData.Add("name", skyDriveFolderName);
        client.PostCompleted += new EventHandler<LiveOperationCompletedEventArgs>(CreateFolder_Completed);
        client.PostAsync("me/skydrive", skyDriveFolderData);
     }
     else
        UploadFile();
}
else
{
     MessageBox.Show(e.Error.Message);
}
}

REST will return “data” text value which holds information about SkyDrive root folder. Convert “data” information into collection of folders and iterate them to check if desired folder exists. In this case it is value of variable skyDriveFolderName.

 

private string skyDriveFolderName = "IsolatedStorageFolder";

If folder exists we will took its folderID value, because that value is needed to define upload folder in next steps. If folder does not exists, we have to create it using PostAsync method. When folder is created following method for handling CreateFolder_Completed event is executed and new folder ID value is assigned to skyDriveFolderID variable:

 

private void CreateFolder_Completed(object sender, LiveOperationCompletedEventArgs e)
{
    if (e.Error == null)
    {
        infoTextBlock.Text = "Folder created.";
        Dictionary<string, object> folder = (Dictionary<string, object>)e.Result;
        skyDriveFolderID = folder["id"].ToString();
        UploadFile();
    }
    else
    {
        MessageBox.Show(e.Error.Message);
    }
}

3) Upload image to SkyDrive’s "IsolatedStorageFolder" folder:

When we are sure that desired folder exists in user’s SkyDrive, last step is uploading file from Isolated Storage.

 

private void UploadFile()
        {
            if (skyDriveFolderID != string.Empty)
            {
                string[] filePathSegments = cameraRollFileName.Split(‘\\’);
                string fileName = filePathSegments[filePathSegments.Length – 1];
                var scopes = new List<string>(1);
                this.client.UploadCompleted
                    += new EventHandler<LiveOperationCompletedEventArgs>(ISFile_UploadCompleted);

                IsolatedStorageFileStream fileStream = null;
                try
                {
                    using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        fileStream = store.OpenFile(cameraRollFileName, FileMode.Open, FileAccess.Read);
                    }

                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }
                this.client.UploadAsync(skyDriveFolderID, cameraRollFileName, true, fileStream, null);
            }
        }

private void ISFile_UploadCompleted(object sender, LiveOperationCompletedEventArgs args)
{
    if (args.Error == null)
    {
        Dictionary<string, object> file = (Dictionary<string, object>)args.Result;
        // For some reason SkyDrive link property is not part of result.
        this.infoTextBlock.Text = "File uploaded.";
    }
    else
    {
        this.infoTextBlock.Text =
            "Error uploading file: " + args.Error.ToString();
    }
}

And this is it.

User is notified that file is uploaded:

image

 

If you check you SkyDrive account, new folder with image will be there:

image

 

Complete working zip solution can be downloaded from here (don’t forget to put your app client ID in MainPage.xaml).

You can download Live SDK Developer Preview from here.