Connecting a Durable Function To SharePoint Online API

I was working with one of my peers Frank Chen on building a workflow that could be called from SharePoint Online. We looked into Microsoft Flow and Logic Apps. Both are great options for integrating with SharePoint Online. However, we decided on another great Azure Resource the Azure Durable Function, because we wanted to provide SharePoint a custom status that could be leveraged for a progress bar, we also had the need to build a separate function that had a workflow that could span hours or even days. To build our completed solution we developed an Azure Durable Function and leveraged SharePoint Framework (SPFx) and SharePoint Online (SPO).

Today, we will talk about how we called the SharePoint API from our Durable function.

Here’s what our architecture looked like, it’s simple, but powerful:

For our solution to work there were three main steps to successfully integrate our components:

  • SharePoint API – To leverage the SharePoint API we had to create an application id that our Azure Durable Function could leverage to make calls.
  • SharePoint Site – We had to enable CDN Hosting from our SharePoint Admin Site, and install our SPFx Application with the proper permissions 

Prior Reading:
As we write this blog, we hope you have prior knowledge of SPFx and Azure Durable Functions, here are a couple resources to get you up to speed:
Introduction to Durable Functions
SharePoint Framework Docs

Durable Function:
We started off by building our durable function, to keep things simple we decided to have our Durable Function to do something simple like move files from one SharePoint Folder to another. Since CSOM isn’t available for netcore at the moment we leveraged the SharePoint Online API to handle all the requests to our SharePoint API. 

Our Durable Function is split into 3 Activities, and we have our Orchestrator that manages the workflow:

  • GetSharePointToken
  • GetFilesFromDropzone
  • HandleSharePointFile

Let’s talk about the GetSharePointToken Activity, as you can probably tell from the name this Activity is responsible for getting the SharePoint Token that will be leveraged in the future calls we make to the SharePoint API, we are authenticating with an AppID. That we created from our Sharepoint site. We won’t be getting into that here, but here’s a good resource (Create SharePoint App ID)

To get our Access Token we need a few key variables to successfully authenticate, here’s our code sample for getting a token id:

         [FunctionName("GetSharePoint Token")]
        public static async Task<string> GetSharePoint Token([ActivityTrigger] string value, ILogger log)
        {

            log.LogInformation("Start Token Retrieval Process");

            HttpClient client = new HttpClient();
            string appId = Environment.GetEnvironmentVariable("AppId");
            string appSecret = Environment.GetEnvironmentVariable("AppSecret");
            string tenantId = Environment.GetEnvironmentVariable("TenantId");
            string tenantName = Environment.GetEnvironmentVariable("TenantName");
            string grant_type = "client_credentials";

            string clientId = string.Format("{0}@{1}", appId, tenantId);
            string clientSecret = appSecret;
            string resource = String.Format("00000003-0000-0ff1-ce00-000000000000/{0}@{1}", tenantName, tenantId);
            string url = String.Format("https://accounts.accesscontrol.windows.net/{0}/tokens/OAuth/2", tenantId);

            string body = String.Format("grant_type={0}", grant_type);
            body += String.Format("&client_id={0}", WebUtility.UrlEncode(clientId));
            body += String.Format("&client_secret={0}", WebUtility.UrlEncode(clientSecret));
            body += String.Format("&resource={0}", WebUtility.UrlEncode(resource));

            StringContent content = new StringContent(body);

            content.Headers.Clear();
            content.Headers.Add("Content-Type", "application/x-www-form-urlencoded");

            HttpResponseMessage responseToken = await client.PostAsync(url, content);

            string accessToken = null;

            if (responseToken.IsSuccessStatusCode)
            {
                string responseBody = await responseToken.Content.ReadAsStringAsync();
                JObject tokenResult = JObject.Parse(responseBody);
                accessToken = tokenResult["access_token"].ToString();
            }

            log.LogInformation("End Token Retrieval Process");

            return accessToken;
        }


The other two activities leverage our SharePoint Token, to make calls to the SharePoint API, we’re calling APIs to get a list of files in a folder with the GetFilesFromDropzone activity, and moving files using the HandleSharePoint File activity.

The Orchestrator pertaining to Durable Functions plays the following important roles:

  • Orchestrator functions define function workflows using procedural code. No declarative schemas or designers are needed.
  • Orchestrator functions can call other durable functions synchronously and asynchronously. Output from called functions can be reliably saved to local variables.
  • Orchestrator functions are durable and reliable. Execution progress is automatically checkpointed when the function “awaits” or “yields”. Local state is never lost when the process recycles or the VM reboots.
  • Orchestrator functions can be long-running. The total lifespan of an orchestration instance can be seconds, days, months, or never-ending.

Our Orchestrator handles the following:

  1. Calls the GetSharePoint Token Activity to get our token
  2. Waits for the token to be retrieved
  3. Passes the token to the next activity GetFilesFromDropzone
  4. Waits for our Object to be retrieved
  5. Loops through our Object, which contains our files
  6. For each file it calls our HandeSharePoint File activity 
  7. Updates our Custom Status that we will leverage for our Progress Bar within SharePoint

Details About Custom Status
To pass a custom status to our SharePoint site, we leveraged the SetCustomStatus that the Azure Durable Function package provides.           

context.SetCustomStatus(new
     {
          Process = (i + 1) * 100 / sharepointResult.Files.Count,
          Message = msg
     });

We know the number of files in our list, so after each file is moved or each process is complete we recalculate the percent completion, and we also provide a message of which file is currently running. This custom status allowed us to create an accurate progress bar.

The code is fairly simple and Durable Functions are easy to use, check out the GitHub Repo to test it out yourself. We’ll also be covering the SPFx portion in a future post.