Azure Function App: upload files to SharePoint from a public URL

This blog will show step by step how to create an Azure Function App to upload files to SharePoint using C#.

This code will receive a public URL that contains a file to download, and we will upload this file to SharePoint, using the Graph API – and by the way, this function was created by our colleague Andhony Hernandez for a real use case we recently worked on!

Ok, let’s get into it!

But first, this is an example of how the request will look: 

public URL

First, let’s define the request structure. 

{function-endpoint}/api/DownloadAndUploadFunction?code={code}&fileName={file-name}&url={public-url}

We will receive 3 query parameters:

  1. Code: this is to authenticate our request, not required if your function is not set up to have authentication
  2. fileName: which name we will save the file as when uploaded to SharePoint
  3. url: public URL that contains the file we want to upload.Steps

We need to complete the following steps to have our function up and running.

  1. Register an application in the Azure portal that grants you access to upload files to SharePoint.
  2. Obtain Site ID, Library ID (drive id) and folder ID (item ID) where your files will be stored.
  3. Create your function code in Visual Studio.
  4. Create Function App in the Azure portal and publish your code.

Table of Contents

Register an application in the Azure portal that grants you access to upload files to SharePoint.

Go to https://portal.azure.com/, and navigate to App registrations

  • Click on New registration
Click on New registration

Assign a name to the registration and select “Accounts in this organizational directory only”.

Register an Application
  • Go to API Permissions and click on Add permission
Go to API Permissions and click on Add permission
  • In the next panel, select Microsoft Graph
Select Microsoft Graph
  • Click on Application permissions
Click on Application permissions
  • In the search bar, find Files.ReadWrite.All, and select it from the results.
Find Files.ReadWrite.All
  • Once added to the list, click on Grant admin consent for [your company name]
Click On Grant Admin Consent
  • Go to the Certifications & secrets blade. Select Client Secrets
Select Client Secrets
  • Click on New client secret, assign a name and define an expiration policy
Click on New client secret
  • Once it’s added, make sure to copy the text under “Value” and store it somewhere safe.
Copy The Text Under “Value”
  • Now, go to the Overview tab and get your tenant ID and client ID
Go To The Overview Tab

You should have the following values at this point:

Client secret

KI68Q~nnY8FojDXub.s1bPnE.6oFeF49LX59Cbip

Client id

a3def885-2bab-4e8f-8fb7-f47f7f2e9e22

tenant id

3131a2ad-2e72-49aa-94fd-552b936e518c

Obtain Site ID, Library ID (drive id) and folder ID (item ID) where your files will be stored

To upload files to SharePoint you need

  • Which site you will upload to (site id)
  • Which library (drive id)
  • Which folder (item id)

We can use the Graph API or Power Automate to obtain these IDs. Below we are using Postman to get them. 

Authentication for the Graph API works with a Bearer token. 

  • First, let’s get the code. Use the same values we grabbed from step 1. 
				
					https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
				
			
Login
  • Get Site ID using below endpoint
				
					https://graph.microsoft.com/v1.0/sites/
				
			
Get Site ID
  • Now, let’s obtain drive id by listing all libraries under the site. 

For our example, we will upload the files into a library called “Upload Files Library”

				
					https://graph.microsoft.com/v1.0/sites/{site-id}/drives/
				
			
Obtain Drive ID
  • Finally, let’s obtain the folder item ID where we will upload files. In this case, we will upload to folder “PDF Files” 
Upload To Folder “Pdf Files”
				
					https://graph.microsoft.com/v1.0/sites/{site-id}/drives/{drive-id}/root/children
				
			
Upload To Folder “Pdf Files”

We now have everything we need to create our function!

Create your function code in Visual Studio

First, create an azure function project and use the HTTP Trigger

Now, let’s write some code. We need a function to authenticate and connect to SharePoint.

				
					    private static async Task<string> GetAccessToken()

    {

        // Azure App registration details

        string clientId = "a3def885-2bab-4e8f-8fb7-f47f7f2e9e22";

        string clientSecret = "KI68Q~nnY8FojDXub.s1bPnE.6oFeF49LX59Cbip";

        // login url - replace 3131a2ad-2e72-49aa-94fd-552b936e518c by your tenant ID

        string authority = "https://login.microsoftonline.com/3131a2ad-2e72-49aa-94fd-552b936e518c/oauth2/v2.0/token";

        string resource = "https://graph.microsoft.com/.default";

        // Get access token

        var clientCredentials = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(clientId, clientSecret);

        var cca = ConfidentialClientApplicationBuilder

            .Create(clientId)

            .WithClientSecret(clientSecret)

            .WithAuthority(new Uri(authority))

            .Build();

        var result = await cca.AcquireTokenForClient(new[] { resource })

            .ExecuteAsync();

        return result.AccessToken;

    }

We need the following variables in our doe

// Get Query parameters

        string url = req.RequestUri.ParseQueryString()["url"];

        string fileName = req.RequestUri.ParseQueryString()["fileName"];

        

        // Site, Drive and Folder files will be uploaded to

        string siteID = "wearepowergi.SharePoint.com,c2ce9ca8-038c-46e7-9440-db1424939b13,282fa06a-5334-4abb-a089-75dbbe2664f4";

        string driveID = "b!qJzOwowD50aUQNsUJJObE2qgLyg0U7tKoIl1274mZPTpI8MqmbRXRoFGWf9LCutP";

        string parentID = "017XKR7NA7ULDT3MDJINEL6MLJW56AEFWG";

        // variable to get file content 

        byte[] fileBytes;

Code to extract file content from URL and upload to SharePoint

        using (HttpClient client = new HttpClient())

        {

            try

            {

                //get file fom URL and assign to variable

                client.Timeout = TimeSpan.FromMinutes(3);

                fileBytes = await client.GetByteArrayAsync(url);

                log.LogInformation($"Downloaded bytes: {fileBytes.Length}");

            }

            catch (Exception ex)

            {

                //if any error happens

                log.LogError($"Error: {ex.Message}");

                log.LogError($"Error: {ex.Data}");

                log.LogError($"Error: {ex.Source}");

                return new StatusCodeResult((int)HttpStatusCode.InternalServerError);

            }

        }

        // Checks if content is not empty

        if (fileBytes == null || fileBytes.Length == 0)

        {

            log.LogError("File content is empty");

            return new BadRequestObjectResult("File content is empry.");

        }

        // endpoint and SharePoint link

        string SharePointUrl = $"https://graph.microsoft.com/v1.0/sites/{siteID}/drives/{driveID}/items/{parentID}:/{fileName}:/content";

        //call GetAccessToken to obtain access token

        string accessToken = await GetAccessToken();

        using (HttpClient client = new HttpClient())

        {

            //start call to graph API

            client.Timeout = TimeSpan.FromMinutes(20);

            // Set Authorization header

            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");

            // Add file content to request body

            ByteArrayContent content = new ByteArrayContent(fileBytes);

            // set headers

            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/plain");

            // call graph API, with all correspondin parameters

            HttpResponseMessage response = await client.PutAsync(SharePointUrl, content);

            response.EnsureSuccessStatusCode();

        }

        log.LogInformation("File downloaded and stored in SharePoint");

        return new OkObjectResult("File downloaded and stored in SharePoint");

Final code should look like this

using System;

using System.Net;

using System.Net.Http;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

using Microsoft.Azure.WebJobs;

using Microsoft.Azure.WebJobs.Extensions.Http;

using Microsoft.Extensions.Logging;

using Microsoft.Identity.Client;

public static class DownloadAndUploadFunction

{

    [FunctionName("DownloadAndUploadFunction")]

    public static async Task<IActionResult> Run(

        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]

        HttpRequestMessage req,

        ILogger log)

    {

       

        // Get Query parameters

        string url = req.RequestUri.ParseQueryString()["url"];

        string fileName = req.RequestUri.ParseQueryString()["fileName"];

     

        // Site, Drive and Folder files will be uploaded to

        string siteID = "wearepowergi.SharePoint.com,c2ce9ca8-038c-46e7-9440-db1424939b13,282fa06a-5334-4abb-a089-75dbbe2664f4";

        string driveID = "b!qJzOwowD50aUQNsUJJObE2qgLyg0U7tKoIl1274mZPTpI8MqmbRXRoFGWf9LCutP";

        string parentID = "017XKR7NA7ULDT3MDJINEL6MLJW56AEFWG";

        // variable to get file content 

        byte[] fileBytes;

        using (HttpClient client = new HttpClient())

        {

            try

            {

                //get file fom URL and assign to variable

                client.Timeout = TimeSpan.FromMinutes(3);

                fileBytes = await client.GetByteArrayAsync(url);

                log.LogInformation($"Downloaded bytes: {fileBytes.Length}");

            }

            catch (Exception ex)

            {

                //if any error happens

                log.LogError($"Error: {ex.Message}");

                log.LogError($"Error: {ex.Data}");

                log.LogError($"Error: {ex.Source}");

                return new StatusCodeResult((int)HttpStatusCode.InternalServerError);

            }

        }

        // Checks if content is not empty

        if (fileBytes == null || fileBytes.Length == 0)

        {

            log.LogError("El contenido del archivo es nulo o vacío. No se realizará la carga en SharePoint.");

            return new BadRequestObjectResult("El contenido del archivo es nulo o vacío. No se realizará la carga en SharePoint.");

        }

        // endpoint and SharePoint link

        string SharePointUrl = $"https://graph.microsoft.com/v1.0/sites/{siteID}/drives/{driveID}/items/{parentID}:/{fileName}:/content";

        //call GetAccessToken to obtain access token

        string accessToken = await GetAccessToken();

        using (HttpClient client = new HttpClient())

        {

            //start call to graph API

            client.Timeout = TimeSpan.FromMinutes(20);

            // Set Authorization header

            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");

            // Add file content to request body

            ByteArrayContent content = new ByteArrayContent(fileBytes);

            // set headers

            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/plain");

            // call graph API, with all correspondin parameters

            HttpResponseMessage response = await client.PutAsync(SharePointUrl, content);

            response.EnsureSuccessStatusCode();

        }

        log.LogInformation("File downloaded and stored in SharePoint");

        return new OkObjectResult("File downloaded and stored in SharePoint");

    }

    private static async Task<string> GetAccessToken()

    {

        // Azure App registration details

        string clientId = "a3def885-2bab-4e8f-8fb7-f47f7f2e9e22";

        string clientSecret = "KI68Q~nnY8FojDXub.s1bPnE.6oFeF49LX59Cbip";

        // login url - replace 3131a2ad-2e72-49aa-94fd-552b936e518c by yout tenant ID

        string authority = "https://login.microsoftonline.com/3131a2ad-2e72-49aa-94fd-552b936e518c/oauth2/v2.0/token";

        string resource = "https://graph.microsoft.com/.default";

        // Get access token

        var clientCredentials = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(clientId, clientSecret);

        var cca = ConfidentialClientApplicationBuilder

            .Create(clientId)

            .WithClientSecret(clientSecret)

            .WithAuthority(new Uri(authority))

            .Build();

        var result = await cca.AcquireTokenForClient(new[] { resource })

            .ExecuteAsync();

        return result.AccessToken;

    }

}
				
			

Create Function App in the Azure portal and publish your code.

Create the resource in Azure for the Azure Function. Below is an example how to set up the resource

Create the resource in Azure for the Azure Function
Now it’s time to Publish our Function to the Azure service. Right click on your solution and select the “Publish” option.
Follow the instructions in the next screens, once you reach the Service Dependencies section, make sure that you select the storage account where your PDF documents to merge are saved. Also, double check that the right container name is used in the connection string.

In below screenshot you can see how I selected storageaccountazure8721 as the one that contains my “temp-pdf” container. Note that this container can be the same where your Function is storing its files or it can a completely different.

Make sure that the container name in the connection string exists in the Storage account you select in the dependency – in this case “temp-pdf”
Click publish and wait for the success confirmation from Visual Studio.

Now you can call your function using the function endpoint and the query parameters!