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)