OneDrive has proven to be very useful over the years. Anybody remembers its ancestor called Live Mesh?
Even if OneDrive offers many ways to be used by users, it might be useful for your applications, including an old-legacy Windows Forms application, to access that feature.
This article will authenticate to a OneDrive library, list available folders and files, download files, and upload files. All this will be done using the Microsoft Graph Client Library for .Net.
The downloadable code
This month’s downloadable demo solution contains both VB and C#. The solution was created using Visual Studio 2017 but can be used in most older versions as well.
I strongly encourage you to download the code as not everything will be pasted here.
Issues with authentication
Apparently, the authentication mechanism in use here will not work for everybody!
For the sake of testing, I am using my OneDrive Personal account and it works like a charm.
If you have issues, have a look at Creating custom authentication provider for OneDriveClient and GraphClient.
I have been told that in some cases, the application will seem to work except that no (or very close to no) files will be returned and showing into the treeview.
Registering your application
Before being able to connect to your OneDrive, you need to register a key to which you can grant some privileges. You can also just delete the key and the application will just stop working.
To create the key, follow these steps;
Figure 1: Registering the application
If your application is not registered, it will just fail when trying to authenticate.
Referencing NuGets
The Microsoft Graph library is available in the form of a NuGet package. Open the “Manage NuGet Packages for the Solution” (by right-click on your solution), search for Graph, select Microsoft.Graph from the list and click the install button. The installation will require some other packages as shown in figure 2. Click on OK and accept the license agreement (after carefully reading it of course!).
Figure 2: Referencing the required NuGets
Still in the NuGets installer, search for “Microsoft.Identity.Client” (you might need to check the “Include prerelease” checkbox in order to see it. Install it too.
Authentication
I have found a useful class named AuthenticationHelper on the web that takes care of the authentication. You can read the code of this class from the downloadable solution.
Remember the application ID I asked you to copy and set aside, it is now the time to use it. At the top of this class, there is a member named _clientId. Paste your own application ID in its value.
When running the demo application and clicking the “Sign In” button, you will find out that the standard dialogs to connect to Office 365/Azure will ask you for your account and your password. Once you have entered the correct information, another dialog will show up (see figure 3) asking you if you allow the access to your application. You need to answer yes otherwise your application will be denied access.
Figure 3: Allowing access to your application
Listing files
Once you have logged in and allowed access, the screen will load the folders you have in your OneDrive into the TreeView control (I have reused code from my March 2006 article.
You can now click the folders and/or files to select/expand/collapse items. Notice that when an item is selected, its properties will be shown in the PropertyGrid control on the right of the screen.
Figure 4: The demo application in action
If you look at the code, you will find out the Microsoft Graph library is used to query asynchronously the OneDrive service in order to get data. In this case, the GetAsync method from GraphClient.Drive.Root is queried because we want all the objects at the root of the OneDrive. We don’t want to start looping as you might have a very large OneDrive and it might take way too long to just display it.
Each object retrieved from OneDrive is used to create nodes in the TreeView. The object itself is stored in the Tag property of each node for later reference.
Here is the most interesting part:
Private Async Function TVW_FillBase() As Task ShowWork(True) tvwOneDrive.Nodes.Clear() Try Dim folder As DriveItem = Await GraphClient.Drive.Root.Request().Expand("thumbnails,children($expand=thumbnails)").GetAsync() If folder IsNot Nothing Then If folder.Folder IsNot Nothing AndAlso folder.Children IsNot Nothing AndAlso folder.Children.CurrentPage IsNot Nothing Then For Each obj In folder.Children.CurrentPage Dim node As TreeNode = tvwOneDrive.Nodes.Add(obj.Name) node.Tag = obj node.Nodes.Add("DUMMY ENTRY") Next End If End If Catch exception As Exception PresentServiceException(exception) End Try ShowWork(False) End Function
private async Task TVW_FillBase() { ShowWork(true); tvwOneDrive.Nodes.Clear(); try { DriveItem folder = await GraphClient.Drive.Root.Request().Expand("thumbnails,children($expand=thumbnails)").GetAsync(); if (folder != null) { if (folder.Folder != null && folder.Children != null && folder.Children.CurrentPage != null) { foreach (var obj in folder.Children.CurrentPage) { TreeNode node = tvwOneDrive.Nodes.Add(obj.Name); node.Tag = obj; node.Nodes.Add("DUMMY ENTRY"); } } } } catch (Exception exception) { PresentServiceException(exception); } ShowWork(false); }
Deleting a file
Deleting an object is not difficult. It resumes to call the DeleteAsync method from the GraphClient.Drive.Items collection.
Private Async Sub btnDelete_Click(sender As Object, e As EventArgs) Handles btnDelete.Click If SelectedItem Is Nothing Then MessageBox.Show("Select an item first") Return End If ShowWork(True) Dim result = MessageBox.Show("Are you sure you want to delete " & SelectedItem.Name & "?", "Confirm Delete", MessageBoxButtons.YesNo) If result = DialogResult.Yes Then Try Await GraphClient.Drive.Items(SelectedItem.Id).Request().DeleteAsync() RefreshNode() MessageBox.Show("Item was deleted successfully") Catch exception As Exception PresentServiceException(exception) End Try End If ShowWork(False) End Sub
private async void btnDelete_Click(object sender, EventArgs e) { if (SelectedItem == null) { MessageBox.Show("Select an item first"); return; } ShowWork(true); var result = MessageBox.Show("Are you sure you want to delete " + SelectedItem.Name + "?", "Confirm Delete", MessageBoxButtons.YesNo); if (result == DialogResult.Yes) { try { await GraphClient.Drive.Items[SelectedItem.Id].Request().DeleteAsync(); RefreshNode(); MessageBox.Show("Item was deleted successfully"); } catch (Exception exception) { PresentServiceException(exception); } } ShowWork(false); }
Downloading a file
Downloading a file is not much more difficult. Here I am using the SaveFileDialog to let the user select a place where to save the file before calling the GetAsync method (again) on the Content object of the GraphClient.Drive.Items collection. This call returns a stream that you can then save into the selected file.
Private Async Sub btnDownload_Click(sender As Object, e As EventArgs) Handles btnDownload.Click If SelectedItem Is Nothing Then MessageBox.Show("Select an item first") Return End If ShowWork(True) Dim item = SelectedItem If item Is Nothing Then MessageBox.Show("Nothing selected.") Return End If Dim dialog = New SaveFileDialog With {.FileName = item.Name, .Filter = "All Files (*.*)|*.*"} If dialog.ShowDialog() <> DialogResult.OK Then Return Using stream = Await GraphClient.Drive.Items(item.Id).Content.Request().GetAsync() Using outputStream = New IO.FileStream(dialog.FileName, IO.FileMode.Create) Await stream.CopyToAsync(outputStream) End Using End Using ShowWork(False) End Sub
private async void btnDownload_Click(object sender, EventArgs e) { if (SelectedItem == null) { MessageBox.Show("Select an item first"); return; } ShowWork(true); var item = SelectedItem; if (null == item) { MessageBox.Show("Nothing selected."); return; } var dialog = new SaveFileDialog { FileName = item.Name, Filter = "All Files (*.*)|*.*" }; if (dialog.ShowDialog() != DialogResult.OK) return; using (var stream = await GraphClient.Drive.Items[item.Id].Content.Request().GetAsync()) using (var outputStream = new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Create)) { await stream.CopyToAsync(outputStream); } ShowWork(false); }
Uploading a file
Uploading a file goes the opposite way. The GetFileStreamForUpload method is used to ask the user for a file to upload. This file is then opened in a stream.
With that stream object in hands, we are now ready to call the PutAsync method of the GraphClient.Drive.Root.ItemWithPath object. The file will be uploaded in the selected folder of the TreeView.
TODO show GetFileStreamForUpload & btnUpload_Click
Private Function GetFileStreamForUpload(ByVal pTargetFolderName As String, ByRef pOriginalFilename As String) As IO.Stream Dim dialog As OpenFileDialog = New OpenFileDialog With {.Title = "Upload to " & pTargetFolderName, .Filter = "All Files (*.*)|*.*", .CheckFileExists = True} Dim response = dialog.ShowDialog() If response <> DialogResult.OK Then pOriginalFilename = Nothing Return Nothing End If Try pOriginalFilename = IO.Path.GetFileName(dialog.FileName) Return New IO.FileStream(dialog.FileName, IO.FileMode.Open) Catch ex As Exception MessageBox.Show("Error uploading file: " & ex.Message) pOriginalFilename = Nothing Return Nothing End Try End Function Private Async Sub btnUpload_Click(sender As Object, e As EventArgs) Handles btnUpload.Click If CurrentFolder Is Nothing Then MessageBox.Show("Select a folder first") Return End If ShowWork(True) Dim targetFolder = CurrentFolder Dim filename As String Using stream = GetFileStreamForUpload(targetFolder.Name, filename) If stream IsNot Nothing Then Dim folderPath As String = If(targetFolder.ParentReference Is Nothing, "", targetFolder.ParentReference.Path.Remove(0, 12) & "/" + Uri.EscapeUriString(targetFolder.Name)) Dim uploadPath = folderPath & "/" + Uri.EscapeUriString(IO.Path.GetFileName(filename)) Try Dim uploadedItem = Await GraphClient.Drive.Root.ItemWithPath(uploadPath).Content.Request().PutAsync(Of DriveItem)(stream) RefreshNode() MessageBox.Show("Uploaded with ID: " & uploadedItem.Id) Catch exception As Exception PresentServiceException(exception) End Try End If End Using ShowWork(False) End Sub
private System.IO.Stream GetFileStreamForUpload(string pTargetFolderName, out string pOriginalFilename) { OpenFileDialog dialog = new OpenFileDialog { Title = "Upload to " + pTargetFolderName, Filter = "All Files (*.*)|*.*", CheckFileExists = true }; var response = dialog.ShowDialog(); if (response != DialogResult.OK) { pOriginalFilename = null; return null; } try { pOriginalFilename = System.IO.Path.GetFileName(dialog.FileName); return new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Open); } catch (Exception ex) { MessageBox.Show("Error uploading file: " + ex.Message); pOriginalFilename = null; return null; } } private async void btnUpload_Click(object sender, EventArgs e) { if (CurrentFolder == null) { MessageBox.Show("Select a folder first"); return; } ShowWork(true); var targetFolder = CurrentFolder; string filename; using (var stream = GetFileStreamForUpload(targetFolder.Name, out filename)) { if (stream != null) { // Since the ItemWithPath method is available only at Drive.Root, we need to strip // /drive/root: (12 characters) from the parent path string. string folderPath = targetFolder.ParentReference == null ? "" : targetFolder.ParentReference.Path.Remove(0, 12) + "/" + Uri.EscapeUriString(targetFolder.Name); var uploadPath = folderPath + "/" + Uri.EscapeUriString(System.IO.Path.GetFileName(filename)); try { var uploadedItem = await GraphClient.Drive.Root.ItemWithPath(uploadPath).Content.Request().PutAsync<DriveItem>(stream); RefreshNode(); MessageBox.Show("Uploaded with ID: " + uploadedItem.Id); } catch (Exception exception) { PresentServiceException(exception); } } } ShowWork(false); }
Conclusion
It might not be a perfect solution just yet. Don’t forget that some libraries used here are still in pre-release.
Of course, there are other (easier) ways. For example, if you sync your OneDrive to your local hard drive, anything written or deleted from there will automatically be reflected to your cloud. It is much easier to interact with a local folder than it is with the Cloud.
But many people with small ultra-books don’t have the luxury of having a local copy of their OneDrive locally (disk space is often limited).
I also encourage you to visit the OneDrive Dev Center.