Diederik Krols

The XAML Brewer

Using the OneDrive SDK in universal apps

This article shows how to use the new OneDrive SDK in a Windows universal app. It presents a sample app with the following features:

  • Singing in with a Windows account.
  • Creating a working folder. 
  • Browsing the content of a working folder.
  • Downloading a file.
  • Uploading a file.
  • Deleting a file.
  • Fetching the changes in a folder since the last check.

Here’s how that app looks like on the PC (it also runs on the phone):

screenshot_04042015_084243

Some weeks ago, Microsoft launched a new OneDrive API with a couple of new features, like easy synchronization and support for very large files. The API also came with three sample projects, one in HTML/Javascript,, one in WinForms/C#, and one for Android (yup: no XAML). The Windows Forms sample contains the source of the new OneDrive SDK. And I quote: ‘An example library for interacting with OneDrive. This library is implemented as a Portable Class Library and can be used on a number of target platforms including .NET Framework 4.5, Windows 8, Windows Phone 8.1, and Windows Phone Silverlight 8’. The SDK is a lightweight wrapper around the OneDrive OData REST API.

Here are some of the key classes in this new SDK:

  • The ODItem class represents a file or a folder on your OneDrive. It contains all their properties, such as path, status, size, and thumbnail.
  • The ODConnection class represents your connection to your OneDrive. It contains the CRUD methods to work with files and folders.
  • The IAuthenticationInfo interface describes the authentication requirements. The SDK does not contain an implementation since that depends on the technology you’re using.

 SDK_API

Authenticating to Live

You have to provide an authentication infrastructure to use the SDK. It expects a class that gets a token for your Windows Account, and that implements the SDK’s IAuthenticationInfo interface. Of course you could rely on the Live SDK to authenticate (instructions can be found here), but the OneDrive SDK is actually a replacement for this Live SDK. So it makes more sense to create (or find) a lightweight class on top of the Live OAuth services. I did not built class from scratch: fortunately I could reuse the authentication and authorization calls from another sample, the OneNote API universal app sample. After all, both OneNote and OneDrive are both Live services that sit behind the same authentication infrastructure.

The authentication is done through an OnlineIdAuthenticator instance.

Here’s the beef of the code:

// TODO: Replace the below scopes with the least permissions your app needs
private const string Scopes = "wl.signin onedrive.readwrite";

// TODO: Replace the below ClientId with your app's ClientId.
private const string ClientId = "<YOUR APP's CLIENT ID HERE>";

private const string MsaTokenRefreshUrl = "https://login.live.com/oauth20_token.srf";
private const string TokenRefreshContentType = "application/x-www-form-urlencoded";
private const string TokenRefreshRedirectUri = "https://login.live.com/oauth20_desktop.srf";

private const string TokenRefreshRequestBody = "client_id={0}&redirect_uri={1}&grant_type=refresh_token&refresh_token={2}";

/// <summary>
/// Gets a valid authentication token. Also refreshes the access token if it has expired.
/// </summary>
/// <remarks>
/// Used by the API request generators before making calls to the OneNote and OneDrive APIs.
/// </remarks>
/// <returns>valid authentication token</returns>
internal static async Task<string> GetAuthToken()
{
    if (String.IsNullOrWhiteSpace(_accessToken))
    {
        try
        {
            var serviceTicketRequest = new OnlineIdServiceTicketRequest(Scopes, "DELEGATION");
            var result =
                await Authenticator.AuthenticateUserAsync(new[] { serviceTicketRequest }, CredentialPromptType.PromptIfNeeded);
            // TODO: catch exception when user says no
            if (result.Tickets[0] != null)
            {
                _accessToken = result.Tickets[0].Value;
                _accessTokenExpiration = DateTimeOffset.UtcNow.AddMinutes(AccessTokenApproxExpiresInMinutes);
            }
        }
        catch (Exception ex)
        {
            // Authentication failed
            if (Debugger.IsAttached) Debugger.Break();
        }
    }
    await RefreshAuthTokenIfNeeded();
    return _accessToken;
}

/// <summary>
///     This method tries to refresh the access token. The token needs to be
///     refreshed continuously, so that the user is not prompted to sign in again
/// </summary>
/// <returns></returns>
private static async Task AttemptAccessTokenRefresh()
{
    var createMessage = new HttpRequestMessage(HttpMethod.Post, MsaTokenRefreshUrl)
    {
        Content = new StringContent(
            String.Format(CultureInfo.InvariantCulture, TokenRefreshRequestBody,
                ClientId,
                TokenRefreshRedirectUri,
                _refreshToken),
            Encoding.UTF8,
            TokenRefreshContentType)
    };

    var httpClient = new HttpClient();
    HttpResponseMessage response = await httpClient.SendAsync(createMessage);
    await ParseRefreshTokenResponse(response);
}

In a universal app, the very first time that GetAuthToken is called, the user has to give consent, based on the Scopes in the code (wl.signin and onedrive.readwrite):

Consent

This only works if your universal app is registered in the Store(s). Otherwise you don’t have a Client ID.

Here’s how to find the Client ID of your app. Register your app in the Store and associate is with your project. Then go to the app’s Services configuration in the Store dashboard. From there you can follow the link to the Live Services site that reveals the Client ID:

app_services

app_live

Connecting to your OneDrive

Once authenticated, all you need to do is present your token to the OneDrive SDK through the IAuthenticationInfo interface to create an ODConnection that gives you access to a all the API calls. To protect my viewmodels from the internals of the SDK, I created my own MyOneDrive class in between. Here’s its Login method:

/// <summary>
/// Login and create the connection, if necessary.
/// </summary>
public static async Task Login()
{
    if (Connection == null || !LiveOAuth.IsSignedIn)
    {
        // Get an OAuth2 access token through REST.           
        var token = await LiveOAuth.GetAuthToken();

        // Initialize connection
        Connection = new ODConnection("https://api.onedrive.com/v1.0", new OneDriveSdkAuthenticationInfo(token));
    }
}

The rest of the methods in that class will throw an error if the user is not authenticated. They all start with a call to EnsureConnection:

private static void EnsureConnection()
{
    if (Connection == null || !LiveOAuth.IsSignedIn)
    {
        throw new Exception("You're not logged in.");
    }
}

Browsing OneDrive content

For walking the OneDrive tree, you start with getting a reference to the root. And that’s just what GetRootItemAsync does:

// Connect to OneDrive root.
var rootFolder = await Connection.GetRootItemAsync(ItemRetrievalOptions.DefaultWithChildren);

From there you can navigate on the Connection, and enumerate or search for folders and files using methods such as GetChildrenOfItemAsync and SearchForItemsAsync. Some of these calls are also available on ODItem as extension methods. For the REST (pun intended) that ODItem class is just a JSON data transfer object. Here’s how the MyOneDrive class fetches the files in a specific folder. Since folders can be large –look at your pictures/camera roll folder- the API provides paging. So you have to iterate:

public static async Task<IEnumerable<ODItem>> GetFiles(ODItem parentItem)
{
    EnsureConnection();

    List<ODItem> result = new List<ODItem>();
    var page = await parentItem.PagedChildrenCollectionAsync(Connection, ChildrenRetrievalOptions.Default);

    var list = from f in page.Collection
                where f.File != null
                select f;

    result.AddRange(list);

    while (page.MoreItemsAvailable())
    {
        list = from f in page.Collection
                where f.File != null
                select f;

        result.AddRange(list);

        page = await page.GetNextPage(Connection);
    }

    return result;
}

To download the contents of a file, just call GetContentsStreamAsync:

public static async Task<Stream> DownloadFile(ODItem item)
{
    EnsureConnection();

    return await item.GetContentStreamAsync(Connection, StreamDownloadOptions.Default);
}

Modifying files and folders

Typically an app that uses OneDrive services would have a working folder that contains all its files. That concept is supported by the OneDrive API through the App Folder: Apps/YourAppName is the pattern. This concept does not exist in the SDK (yet).

The app’s working folder needs to be created. Here’s the method that I call after signing in. It looks for a folder with a specific name (directly under the root folder), and creates it if necessary:

public static async Task<ODItem> SetWorkingFolder(string folderName)
{
    EnsureConnection();

    // Connect to OneDrive root.
    var rootFolder = await Connection.GetRootItemAsync(ItemRetrievalOptions.DefaultWithChildren);

    // Search working folder
    var existingItem = (from f in rootFolder.Children
                        where f.Name == folderName
                        select f).FirstOrDefault();

    if (existingItem != null)
    {
        if (existingItem.Folder == null)
        {
            // There is already a file with this name.
            throw new Exception("There is already a file with this name.");
        }
        else
        {
            // The folder already exists.
            WorkingFolder = existingItem;
            return WorkingFolder;
        }
    }

    var newFolder = await Connection.CreateFolderAsync(rootFolder.ItemReference(), folderName);
    WorkingFolder = newFolder;
    return WorkingFolder;
}

To upload a new or a modified file, just call PutNewFileToParentItemAsync:

public static async Task<ODItem> SaveFile(ODItem parentItem, string filename, Stream fileContent)
{
    EnsureConnection();

    return await Connection.PutNewFileToParentItemAsync(parentItem.ItemReference(), filename, fileContent, ItemUploadOptions.Default);
}

Of course, there’s also a DeleteItemAsync:

public static async Task<bool> DeleteItem(ODItem item)
{
    EnsureConnection();

    return await Connection.DeleteItemAsync(item.ItemReference(), ItemDeleteOptions.Default);
}

As you see, CRUD operations on OneDrive files and folders are easy.

Enumerating changes

A fantastic new feature in the OneDrive API is the ability to check for changes in a folder in an incremental way. The ViewChangesAsync method returns not only the changes to a folder, but also a token that the app can store (e.g. in its settings). That token can then be presented in the next call to ViewChangesAsync to get the delta. The ODViewChangesResult contains details on the items that were added, modified, or deleted since the token was generated.

There are some apps out there that use a OneDrive folder to synchronize their working data across devices. With the ‘old’ Live SDK that is a hard and error prone job (sample code here), with the new OneDrive API this becomes a piece of cake.

Here’s the ViewChanges from the MyOneDrive class. It stores the token in a variable. It also allows to reset the token, in which case a full list of children from the folder is returned. I’ll explain this in a minute:

private static string Token = string.Empty;

/// <summary>
/// Returns the list of changes to the working folder since the previous call.
/// </summary>
/// <param name="reset">Whether or not to reset the token.</param>
/// <remarks>In the first call and whenever the token is reset, the whole folder's content is returned.</remarks>
public static async Task<ODViewChangesResult> ViewChanges(bool reset = false)
{
    EnsureConnection();

    if (reset)
    {
        Token = null;
    }

    var options = string.IsNullOrEmpty(Token) ? ViewChangesOptions.Default : new ViewChangesOptions() { StartingToken = Token };

    var result = await Connection.ViewChangesAsync(WorkingFolder.ItemReference(), options);
    Token = result.NextToken;

    return result;
}

In a real-world app, I assume that paging will also be required here. The default is 200 changes per page.

Wrap-up

Here are the classes that I created to use the new OneDrive SDK in a universal app:

  • LiveOAuth takes care of the authentication. It’s largely copied from the sample OneNote authenticator.
  • OneDriveSdkAuthenticationInfo makes the previous class compatible with the OneDrive SDK by implementing the IAuthentication interface.
  • MyOneDrive is an MVVM service that provides a friendly facade to the SDK.

This is the corresponding class diagram:

My_API

The full documentation of the new OneDrive SDK is coming soon, I also assume that Microsoft will publish it as a Nuget package.

I’m already a fan of this SDK: it’s more lightweight than the full Live SDK (which requires a local install on the development machine) and it comes with very useful new features. It’s also good to see the full source on GitHub, since some of our customers don’t like black box dll’s in their projects.

A warning for Windows Phone developers

Due to asynchronicity and caching there may be some delays in getting the exact current state of your OneDrive content. The following animation shows the sample app running on a phone. You’ll notice the following:

  • User tries to browse content without signing in, and gets an error.
  • User signs in.
  • User fetches content of the working folder. 8 sample files are returned.
  • User selects file 1, and successfully deletes it.
  • User fetches changes. The token was empty so the full content of the working folder is returned. File 1 is not in this list.
  • User selects file 2, and successfully deletes it.
  • User fetches changes. The deletion of file 2 is returned.
  • User browses content of working folder. File 1 and File 2 are still returned.
  • User fetches changes with an empty token. File 2 is still in this list.

 Phone_Sample

I was not able to reproduce this delay on the PC. There the information was always up-to-date, after all a PC is continuously synchronizing that drive.

So you may not assume that the information is real-time. But don’t let that stop you: it’s probably real-enough time.

Source code

The full source code of the sample includes a non-maintained version of the source code of the OneDrive SDK, and lives on GitHub. It was written in Visual Studio 2013. [The odds are high that this will be my last blog project in that version.]

Enjoy!

XAML Brewer

An animated ScrollToSection for the Universal Hub Control

n this article I show a way to animate the scrolling to a particular HubSection of a Hub in a Universal XAML Windows app. The native Hub.ScrollToSection method comes without such an animation: instead of smoothly scrolling, it jumps directly to the target section. That’s quite scary. A lot of developers seem to encounter this problem, but I didn’t find a working solution anywhere. So here are my 2 cents.

The sample app represents the type of Windows Phone application that I created this animation for. The app’s main structure is a Hub. The first HubSection in this hub acts a menu: it contains a list of hyperlinks to the other sections in the hub. The rest of the navigation buttons are just there to verify the algorithm: it should be possible to scroll from whatever section to whatever other section, backward as well as forward:

wp_ss_20150223_0001 wp_ss_20150223_0008 wp_ss_20150223_0005 wp_ss_20150223_0006

 

The Hub itself does not have a HorizontalOffset or so. The property we need to animate here is found in its internal structure: the HorizontalOffset of the embedded ScrollViewer control. Unfortunately this is a read-only property, we can not change it through a DoubleAnimation in a StoryBoard. The scroll position can only be changed by calling one of the ChangeView method overloads. Oddly enough, if you want animation, you have to call the ChangeView that allows you to disable … animation. Here’s the official description of the disableAnimation parameter: “True to disable zoom/pan animations while changing the view; otherwise, false. The default is false”. Well, that default value ruins scrolling on the phone. The default animation is visually empty, but it probably takes some time and during that time it absorbs your own animations. So you have to explicitly disable the system animation by setting the parameter to True.

The confusion doesn’t stop here. This is the official description of the horizontalOffset parameter, the first in the ChangeView call: “A value between 0 and ScrollableWidth that specifies the distance the content should be scrolled horizontally.” To me that  sounds like the method expects a delta; e.g. +10 if you want to scroll 10 pixels to the right. It turns out that this horizontalOffset is actually the target position: with +10 you navigate to the 10th pixel in the Hub.

So here’s a working implementation of smoothly scrolling to a specific section in a hub. We first calculate the absolute current position and the relative position of the target section. Then we start to scroll towards the target in a number of steps, with an increasing wait time between the steps to simulate an easing function. That sounds cheap, but it does the trick:

public async static Task ScrollToSectionAnimated(this Hub hub, HubSection section)
{
    // Find the internal scrollviewer and its current horizontal offset.
    var viewer = hub.GetFirstDescendantOfType<ScrollViewer>();
    var current = viewer.HorizontalOffset;

    // Find the distance to scroll.
    var visual = section.TransformToVisual(hub);
    var point = visual.TransformPoint(new Point(0, 0));
    var offset = point.X;

    // Scroll in a more or less animated way.
    var increment = offset / 24;
    for (int i = 1; i < 25; i++)
    {
        viewer.ChangeView((i * increment) + current, null, null, true);
        await Task.Delay(TimeSpan.FromMilliseconds(i * 20));
    }
}

The ScrollToSectionAnimated method is implemented as an extension method. I added some extra methods in the surrounding class, to fetch the current section of the hub, and the index of this section. These make particularly sense in a Windows Phone app, where there is only one section visible. That’s the first one in Hub.SectionsInView:

public static HubSection CurrentSection(this Hub hub)
{
    if (hub.Sections.Count > 0)
    {
        return hub.SectionsInView[0];
    }
    else
    {
        return null;
    }
}

public static int CurrentIndex(this Hub hub)
{
    if (hub.Sections.Count > 0)
    {
        return hub.Sections.IndexOf(hub.CurrentSection());
    }
    else
    {
        return -1;
    }
}

So here’s the code to scroll to the next and the last section, starting from the current position in the hub:

private async void ScrollToNext_Clicked(object sender, RoutedEventArgs e)
{
    // Find out where we are.
    var index = this.MainHub.CurrentIndex();

    await this.MainHub.ScrollToSectionAnimated(this.MainHub.Sections[index + 1]);
}

private async void ScrollToLast_Clicked(object sender, RoutedEventArgs e)
{
    await this.MainHub.ScrollToSectionAnimated(this.MainHub.Sections.Last());
}

Here's how the scrolling looks like in action: