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