30DaysMSGraph – Day 29 – Use case: Upload files to OneDrive

Brian T. Jackett

List of all posts in the #30DaysMSGraph series

-Today’s post written by Jeremy Kelley

In Day 28 we created a webhook notification for Microsoft Graph events.  Today we will interact with files stored in OneDrive.

Prerequisites

For today’s sample we’ll be using Visual Studio Community edition.  We recommended that you have finished the basic console application sample from Day 15.

To follow along with the sample, you can either download the provided sample code for Day 29 [Add link to GitHub] or follow these setup instructions:

  1. Install Visual Studio Community Edition
  2. Create a new blank Universal Windows application
  3. Add the following NuGet packages
    • Microsoft.Graph
    • Microsoft.Identity.Client
  4. Add the following using declarations to your MainPage class:
using Microsoft.Identity.Client; 
using System.Threading.Tasks; 
using System.Net.Http.Headers; 
using Windows.Storage; 
using Windows.Storage.Pickers;
  1. In MainPage.xaml.cs add the following code below the MainPage constructor:
    private const string AADClientId = "YOURAPPIDHERE"; 
    private const string GraphAPIEndpointPrefix = "https://graph.microsoft.com/v1.0/"; 
    private string[] AADScopes = new string[] { "files.readwrite.all" }; 
    private PublicClientApplication AADAppContext = null; 
    private GraphServiceClient graphClient = null; 
    private AuthenticationResult userCredentials; 
    public AuthenticationResult UserCredentials 
    { 
        get { return userCredentials; } 
        set { userCredentials = value; } 
    } 

    public void InitializeGraph() 
    { 
        if (userCredentials != null) 
        { 
            graphClient = new GraphServiceClient( 
                GraphAPIEndpointPrefix, 
                new DelegateAuthenticationProvider( 
                    async (requestMessage) => 
                    { 
                        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", userCredentials.AccessToken); 
                    } 
                ) 
            ); 
        } 
    } 

    /// <summary> 
    /// Log the user in to either Office 365 or OneDrive consumer 
    /// </summary> 
    /// <returns>A task to await on</returns> 
    public async Task<string> SignInUser() 
    { 
        string status = "Unknown"; 

        // Instantiate the app with AAD 
        AADAppContext = new PublicClientApplication(AADClientId); 

        try 
        { 
            UserCredentials = await AADAppContext.AcquireTokenAsync(AADScopes); 
            if (UserCredentials != null) 
            { 
                status = "Signed in as " + UserCredentials.Account.Username; 
                InitializeGraph(); 
            } 
        } 

        catch (MsalServiceException serviceEx) 
        { 
            status = $"Could not sign in, error code: " + serviceEx.ErrorCode; 
        } 

        catch (Exception ex) 
        { 
            status = $"Error Acquiring Token: {ex}"; 
        } 

        return (status);
    }
  1. Replace YOURAPPIDHERE with the id of your application created in the basic console application example.

Working with files

Files are the backbone of many tasks you might want to do with OneDrive or SharePoint using the Microsoft Graph APIs.  When working with files the first thing to start with is always a Drive.  Drives can be a user’s OneDrive or a Document Library in a SharePoint site.

Accessing the files in OneDrive is as easy as accessing the /me/drive endpoint.  From that starting point you can enumerate the children, download content, upload new files, and even do things like convert content from one format to another.

In SharePoint you first need to address the site that the file is in.  You can do this in one of several ways.

List all drives in a site by Id:

GET /sites/{id}/drives

Get the default drive in a site by Id:

GET /sites/{id}/drive

You can also access sites by path if you don’t happen to know the Id.

List all drives in a site by path:

GET /sites/TENANTHOSTURL:/SITEPATH:/drives

Get the default drive in a site by path:

GET /sites/TENANTHOSTURL:/SITEPATH:/drives

In this post and the associated code sample we’re going to look at how to upload files to either the currently logged in user’s OneDrive for Business or the default document library in the root site collection of the signed in user’s tenant.

You can easily adapt this code to upload to other locations as you see fit.

Add required scopes

When working with files you can ask for one of several permissions.  To access only the user’s OneDrive ask for Files.Read or Files.ReadWrite.  To access or create files anywhere the user has access use Files.Read.All or Files.ReadWrite.All.

To enable our code to upload files to either OneDrive or SharePoint we’ll use Files.ReadWrite.All.

Since you didn’t assign the Files.ReadWrite.All permission in a previous day ensure that you assign that now to your Azure AD application.  Navigate to the Preview App Registrations experience in the Azure AD portal and assign the delegated permission for Files.ReadWrite.All under Microsoft Graph API.

Upload a small file

For files less than 4MB you can use a PUT to upload the file directly to the location you want.  To implement the upload method, add the code below to your MainPage class.

&nbsp;&nbsp;&nbsp; /// <summary>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;/// Take a file and upload it to the service&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;/// </summary>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;/// <param name="fileToUpload">The file that we want to upload</param>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;/// <param name="uploadToSharePoint">Should we upload to SharePoint or&nbsp;OneDrive?</param>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;public async Task&nbsp;UploadSmallFile(StorageFile&nbsp;fileToUpload, bool&nbsp;uploadToSharePoint)&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Stream&nbsp;fileStream&nbsp;= (await&nbsp;fileToUpload.OpenReadAsync()).AsStreamForRead();&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DriveItem&nbsp;uploadedFile&nbsp;= null;&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Do we want OneDrive for Business/Consumer or do we want a SharePoint Site?&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (uploadToSharePoint)&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uploadedFile&nbsp;= await graphClient.Sites["root"].Drive.Root.ItemWithPath(fileToUpload.Name).Content.Request().PutAsync<DriveItem>(fileStream);&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;uploadedFile&nbsp;= await&nbsp;graphClient.Me.Drive.Root.ItemWithPath(fileToUpload.Name).Content.Request().PutAsync<DriveItem>(fileStream);&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;
&nbsp;&nbsp;&nbsp; }

Upload a large file

To upload files larger than 4MB we need to use the CreateUploadSession pattern.  In this pattern we instruct the service to begin a session that we will then upload chunks of data to one at a time.  To implement the large upload pattern add the following method to your class:

    /// &lt;summary&gt; 
    /// Take a file greater than 4MB and upload it to the service 
    /// &lt;/summary&gt; 
    /// &lt;param name="fileToUpload"&gt;The file that we want to upload&lt;/param&gt; 
    /// &lt;param name="uploadToSharePoint"&gt;Should we upload to SharePoint or OneDrive?&lt;/param&gt; 
    public async Task&lt;DriveItem&gt; UploadLargeFile(StorageFile fileToUpload, bool uploadToSharePoint) 
    { 
        Stream fileStream = (await fileToUpload.OpenReadAsync()).AsStreamForRead(); 
        DriveItem uploadedFile = null; 
        UploadSession uploadSession = null; 

        // Do we want OneDrive for Business/Consumer or do we want a SharePoint Site? 
        if (uploadToSharePoint) 
        { 
            uploadSession = await graphClient.Sites["root"].Drive.Root.ItemWithPath(fileToUpload.Name).CreateUploadSession().Request().PostAsync(); 
        } 
        else 
        { 
            uploadSession = await graphClient.Me.Drive.Root.ItemWithPath(fileToUpload.Name).CreateUploadSession().Request().PostAsync(); 
        } 

        if(uploadSession != null) 
        {
            // Chunk size must be divisible by 320KiB, our chunk size will be slightly more than 1MB 
            int maxSizeChunk = (320 * 1024) * 4; 
            ChunkedUploadProvider uploadProvider = new ChunkedUploadProvider(uploadSession, graphClient, fileStream, maxSizeChunk); 
            var chunkRequests = uploadProvider.GetUploadChunkRequests(); 
            var exceptions = new List&lt;Exception&gt;(); 
            var readBuffer = new byte[maxSizeChunk]; 

            foreach (var request in chunkRequests) 
            { 
                var result = await uploadProvider.GetChunkRequestResponseAsync(request, readBuffer, exceptions); 

                if(result.UploadSucceeded) 
                { 
                    uploadedFile = result.ItemResponse; 
                } 
            } 
        } 
        return (uploadedFile); 
    }

Add an interface

Now we need to add some interface elements to our project to allow us to pick files and upload them.

In your MainPage.xaml file replace the default Grid element with the following markup:

<RelativePanel>&nbsp;
&nbsp;&nbsp;&nbsp; <RelativePanel&nbsp;RelativePanel.AlignHorizontalCenterWithPanel="True"&nbsp;RelativePanel.AlignVerticalCenterWithPanel="True">&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <CheckBox&nbsp;x:Name="uploadToSharePointCheckBox" Margin="10,10,10,10">Upload to SharePoint?</CheckBox>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <Button&nbsp;x:Name="uploadSmallFileButton" Margin="10,10,10,10" Click="uploadSmallFileButton_Click"&nbsp;RelativePanel.Below="uploadToSharePointCheckBox"&nbsp;RelativePanel.AlignHorizontalCenterWithPanel="True">Upload small file</Button>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <Button&nbsp;x:Name="uploadLargeFileButton" Margin="10,10,10,10" Click="uploadLargeFileButton_Click"&nbsp;RelativePanel.Below="uploadSmallFileButton"&nbsp;RelativePanel.AlignHorizontalCenterWithPanel="True">Upload large file</Button>&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <TextBlock&nbsp;x:Name="statusTextBlock" Margin="10,10,10,10"&nbsp;RelativePanel.Below="uploadLargeFileButton"&nbsp;RelativePanel.AlignHorizontalCenterWithPanel="True" />&nbsp;
&nbsp;&nbsp;&nbsp; </RelativePanel>&nbsp;
</RelativePanel>

In your code behind (MainPage.xaml.cs) file add the following code to connect the buttons to call our upload methods:

    private async Task&lt;StorageFile&gt; PickFile() 
    { 
        var picker = new FileOpenPicker(); 
        picker.ViewMode = PickerViewMode.Thumbnail; 
        picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; 
        picker.FileTypeFilter.Add(".jpg"); 
        picker.FileTypeFilter.Add(".jpeg"); 
        picker.FileTypeFilter.Add(".png"); 

        StorageFile pickedFile = await picker.PickSingleFileAsync(); 
        return (pickedFile); 
    } 

    private async Task UploadFile(object whichButton) 
    { 
        if (this.UserCredentials == null) 
        { 
            await SignInUser(); 
        } 

        StorageFile fileToUpload = await PickFile(); 
        DriveItem uploadedFile = null; 

        if (whichButton == this.uploadSmallFileButton) 
        { 
            uploadedFile = await UploadSmallFile(fileToUpload); 
        } 
        else 
        { 
            uploadedFile = await UploadLargeFile(fileToUpload); 
        } 
            
        if (uploadedFile != null) 
        { 
            this.statusTextBlock.Text = "Uploaded file: " + uploadedFile.Name; 
        } 
        else 
        { 
            this.statusTextBlock.Text = "Upload failed"; 
        } 
    } 

    private async void uploadSmallFileButton_Click(object sender, RoutedEventArgs e) 
    { 
        await UploadFile(sender);             
    } 

    private async void uploadLargeFileButton_Click(object sender, RoutedEventArgs e) 
    { 
        await UploadFile(sender); 
    }

Beyond upload

Uploading files is just the beginning.  When you upload files to SharePoint you can combine file operations with metadata to fulfill scenarios tailored to your needs.  If you are working in large scale scenarios please check out our best practices for discovering files and detecting changes at scale.

Try It Out

Navigate to the 30DaysMSGraph-TryItOut repo. Clone the repo and configure the project in the Day 29 sub-folder using steps from the README or this post.

If you run into any issues while building or configuring the project, please create a new Issue on the repo.

Join us tomorrow as we wrap up the 30 Days of Microsoft Graph series with community resources and next steps suggestions in Day 30.

Feedback usabilla icon