Modern interaction with Office365 using API and FaaS architecture (Part 2)

Welcome back! 🙂

Remember the objective : creating a small API that accepts a binary stream and save the content to an Office365 Sharepoint document library (using OneDrive API).
In the final post, we will discuss the hosting strategy for this function.

As C# is , currently, my development language of preference, the function is created in C#. 🙂

Let us dive into the code and start authenticating to the OneDrive API and retrieve an access token.

Authenticate to the OneDrive API and retrieve access token (JWT)

In this method we will authenticate to the OneDrive API using the ADAL library.  This library (exposed via f.e. NuGet repository) can be easily hooked in.


private static async Task GetAccessTokenAsync(TraceWriter log)
{
    AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/<YourTenantId>/oauth2/authorize", false);

    string pathToFile = @"<YourPrivateKeyFile>.pfx";

    X509Certificate2 cert = new X509Certificate2(pathToFile, "<YourInsanePassword>", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
    ClientAssertionCertificate cac = new ClientAssertionCertificate("<YourAzureActiveDirectoryApplicationId>", cert);
    var authenticationResult = await authenticationContext.AcquireTokenAsync("https://<YourAzureTenantName>.sharepoint.com", cac);

    return authenticationResult.AccessToken;
}

It is adviced to copy the AccessToken value and decode the Access Token on the JWT-validator website https://jwt.io  In the data field , you should be able to view the application permissions you have specified for the Azure AD Application.
If the specified permissions are not correctly shown, the Administrator consent has not been performed.

Retrieve the drive of the SharePoint document library

In this method, we will retrieve the Sharepoint document library URI (Drive) to store our files in.  Because we want the function to support multiple document libraries, we create a variable for the document library name 🙂  At the final stage, we will expect this argument to be included in the request. (in the header or querystring)


private static async Task GetDocumentLibraryUri(string tenantName, string token, string documentLibraryName)
{
  string uri = string.Empty;

  using (var client = new HttpClient())
  {
    var url = "https://<YourTenantName>.sharepoint.com/_api/v2.0/drives/";
    # Attach access Token to the Authorization header
    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

    var content = new StringContent("");
    var response = client.GetAsync(url).Result;
    var contents = await response.Content.ReadAsStringAsync();

    var searchDocumentLibrary = JObject.Parse(contents)["value"]?.ToObject<List>().Where(
 x => x.Name.Equals(documentLibraryName, StringComparison.InvariantCultureIgnoreCase));

    if (searchDocumentLibrary.Count() == 1)
      uri = searchDocumentLibrary.ElementAt(0).id;
   }
   return uri;
 }

# Small Data Transfer class to map the received JSON
 public class DriveResult
 {
   [JsonProperty("@odata.id")]
   public string id { get; set; }

   [JsonProperty("name")]
   public string Name { get; set; }
 }

Almost there.  Now create a method that does the “heavy” work to upload the file to the document library URI.

Upload the file to the document library URI

Take the stream content and perform a HTTP PUT to the OneDrive retrieved URI.  More information about the OneDrive API can be found here.

private static async Task UploadFileAsync(string baseUri, string token, byte[] contents, string path, string filename)
{
   string uri = string.Format("{0}/root:/{1}/{2}:/content", baseUri, path, filename);

   using (var client = new HttpClient())
   {
      # Attach access Token to the Authorization header
      client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

      long length = contents.Length;
      var contentFile = new ByteArrayContent(contents);
      contentFile.Headers.Add("Content-Length", length.ToString());

      var responseUploadFile = client.PutAsync(uri, contentFile).Result;
      var content = await responseUploadFile.Content.ReadAsStringAsync();
   }
}

Define the input request-variables

To create a function that can be used for the organisation , the function should be able to target a specific folder and a specific document library.
To achieve this, we read the request headers.(it could also be on the QueryString).

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
   log.Info($"C# HTTP function processed a request. RequestUri={req.RequestUri}");
   bool result = false;
   try
   {
      string fileName = string.Empty;
      string directoryName = string.Empty;
      string documentLibraryName = string.Empty;

      if (req.Headers.Contains("filename"))
         fileName = req.Headers.GetValues("filename").First();

      if (req.Headers.Contains("directory"))
         directoryName = req.Headers.GetValues("directory").First();

      if (req.Headers.Contains("documentlibraryname"))
         documentLibraryName = req.Headers.GetValues("documentlibraryname").First();

      if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(documentLibraryName))
         throw new UnauthorizedAccessException(); // do not accept the request

      var fileStream = await req.Content.ReadAsStreamAsync();

      string token = await GetAccessTokenAsync(log);
      var documentLibraryUri = await GetDocumentLibraryUri(tenantName, token, documentLibraryName);
      if ( !string.IsNullOrEmpty(documentLibraryUri))
      {
         await UploadFileAsync(documentLibraryUri, token, ((MemoryStream)fileStream).ToArray(), directoryName, fileName);
         result = true;
      }
   }
   catch(Exception e)
   {
      log.Info("Exception " + e.ToString());
   }
   return new HttpResponseMessage((result == true) ? HttpStatusCode.OK : HttpStatusCode.InternalServerError);
}

I hope you enjoyed this post.

In the next post , we will discuss how we will efficiently host this function as a “Function As a Service”.

Modern interaction with Office365 using API and FaaS architecture (Part 1)

Modern interaction with Office365 using API and FaaS architecture (Part 1)

The Office365 product suite is becoming very mature, appealing for the enhanced collaboration, multi-device support, feature rich, … This new way of thinking / working aligns with the vision of modern companies to empower their employees.

Some companies are noticing those advantages and are moving their data from their local infrastructure (NAS/SAN/…) to OneDrive for Business or SharePoint.  A complete comparison between the two products can be found here.

But when there are custom-made business applications running that write files to the local infrastructure, how can they persist to Office365 fast and easily with minor changes?
Let us tackle this challenge !

Define the requirements

Implement a solution that is highly scalable, available, secure, easily consumable and with small impact / aftercare for Operation-teams.  This solution would also offer a small abstraction to the Office365 implementation.

Implement the requirements

Write a small HTTP API that accepts a binary data stream (= contents of the file) and a few arguments (f.e. file name, directory name, library name)
-> Easily consumable : Check

How can we meet the highly scalable, available, secure and small impact on Operations requirements?  Imagine a platform in which you don’t have to manage the infrastructure needed and is event-driven…  Sounds interesting?  Read on..

Even when using a higher abstraction platform like PaaS, there is still a need to manage the cluster-size , auto-scaling groups, … The following questions are still valid

  • how can you efficiently set the scaling size?
  • how can you predict how many (concurrent) calls the service will receive over time?
  • how can you avoid getting a bill for idle infrastructure?
  • how can you align the infrastructure with the number of received events?

Let us introduce Function As a Service (FaaS) architectures.

Imagine where the developer writes a small dedicated function for the desired behaviour and sends the code to its Cloud provider of choice.
This provider offers an Event-driven system where an event is taken care of a running instance of your application.

Crucial features like up-scaling, down-scaling, availability, Operations, … are the responsability of the provider. The infrastructure becomes event-driven.
Additional advantage when the application is not receiving a request, there is no additional charge because well actually, there are no idling servers. 🙂

azure-functions

Now back to the implementation..

Consuming Office365 API

To empower an application with the rich, intelligent data from Office 365 and Azure AD all from a single endpoint, Microsoft Graph API is ideally suited for this task.  As Microsoft Graph API fully supports daemon applications / services, not all functionality of Office365 is currently supported.  (like f.e. uploading large files for daemon applications)

As OneDrive API is a really nice, complete and supported API for Office365 OneDrive for Business / Personal and Sharepoint, let us consume this API to achieve our goal.

Setting up secure authentication to OneDrive API

The custom-made function (API) is a daemon application that logins in automatically to OneDrive API.  In order to achieve this, the following steps should be taken :

  1. Create an Azure Active Directory Application, Type “Web API”.
    Add a reply Url f.e. https://localhost/uploadFile
  2. Create a certificate and update the Azure Active Directory Application (Manifest file) with this information.  Technical instructions can be found here.
  3. Add the following Application permissions to the Azure Active Directory application:
    azure-aad-permissions
  4. (Optionally) Granting the Administrator consent

We are ready to focus on the implementation code and the hosting part.  These topics really deserve a second post.

Coming very soon your way out …