How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token – Part 2 - Netwoven
Blog

How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token – Part 2

By Subhankar Roy  |  Published on March 9, 2021

How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token

Introduction

In continuation to the previous part of the series, where we created an AAD app, In this part we progress to create a C# console application to fetch AAD token by user login prompt and use the generated token to upload large files using Yammer API.

The pre-requisites remain the same as the previous article.

Creating the App

  1. Open Visual studio and create one C# (.NET Framework) solution of type Console Application.
  2. After the solution is created add NuGet package reference of the following packages
    • Microsoft.Identity.Client (For AAD Token generation)
    • Restsharp (For JSON Deserialization)
    • Newtonsoft.Json (For Making Rest Calls)
  3. Add the Azure AD App ID, Tenant ID and Yammer Network ID as following in the app.config. Replace with Correct values as per your AD App and Tenant settings.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  <appSettings>
    <add key="TenantId" value="XXXXX-XXXX-XXXXX-XXXX-XXXXXXX" />
    <add key="AzureADAppClientID" value="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" />
    <add key="YammerNetworkID" value="0000000" />
  </appSettings>
</configuration>

Adding Helper and Model Classes

We are going to use two Helper files “MicrosoftIdentityClientTokenHelper.cs”, “MicrosoftTokenCacheHelper.cs” for Microsoft Identity Client configuration and token cashing and Model classes for capturing responses from Yammer API.

MicrosoftIdentityClientTokenHelper.cs:

public static class MicrosoftIdentityClientTokenHelper
    {
        static MicrosoftIdentityClientTokenHelper()
        {
            if (!string.IsNullOrEmpty(ClientId) && !string.IsNullOrEmpty(Tenant))
            {
                _clientApp = PublicClientApplicationBuilder.Create(ClientId)
                    .WithAuthority($"{Instance}{Tenant}")
                    .WithDefaultRedirectUri()
                    .Build();
                MicrosoftTokenCacheHelper.EnableSerialization(_clientApp.UserTokenCache);
            }
        }
        private static string ClientId = ConfigurationManager.AppSettings["AzureADAppClientID"];

        private static string Tenant = ConfigurationManager.AppSettings["TenantId"];
        private static string Instance = "https://login.microsoftonline.com/";
        private static IPublicClientApplication _clientApp;

        public static IPublicClientApplication MicrosoftIdentityPublicClientApp { get { return _clientApp; } }
    }

MicrosoftTokenCacheHelper.cs:

public static class MicrosoftTokenCacheHelper
    {
        public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";

        private static readonly object FileLock = new object();

        public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            lock (FileLock)
            {
                args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),null,
                    DataProtectionScope.CurrentUser): null);
            }
        }

        public static void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            if (args.HasStateChanged)
            {
                lock (FileLock)
                {
                    File.WriteAllBytes(CacheFilePath,ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),null,DataProtectionScope.CurrentUser));
                }
            }
        }

        internal static void EnableSerialization(ITokenCache tokenCache)
        {
            tokenCache.SetBeforeAccess(BeforeAccessNotification);
            tokenCache.SetAfterAccess(AfterAccessNotification);
        }
    }

Models.cs

public class YammerCreateFileUploadSessionResponse
    {
        public string url { get; set; }
        public string filename { get; set; }
        public long uploaded_file_id { get; set; }
        public long uploaded_file_version_id { get; set; }
        public bool is_new_file { get; set; }
        public string storage_type { get; set; }
        public string used_path { get; set; }
    }

    public class YammerCompleteDirectUploadSessionResponse
    {
        public long id { get; set; }
        public int network_id { get; set; }
        public string url { get; set; }
        public string web_url { get; set; }
        public string type { get; set; }
        public string name { get; set; }
        public string original_name { get; set; }
        public string full_name { get; set; }
    }
    public class YammerUploadToSharepointResponse
    {
        public Uri ContentDownloadUrl { get; set; }
        public string id { get; set; }
    }

Getting AAD Token for Yammer using Microsoft Authentication Library (MSAL)

Please use the following code to show the Microsoft Authentication Prompt where the user can enter his/her credential and login.

How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token
Following code block tries to get the token in the below mentioned way:
  • If the user information is already cached and token is still valid existing token is returned
  • If user info is cached but token has expired, then it automatically renews the token using renew token
  • If any of the above does not works then it shows the above prompt for sign-in
public static string GetYammerBearerToken(IPublicClientApplication oMicrosoftIdentityPublicClientApp)
        {
            AuthenticationResult authResult = null;
            string[] scopes = new string[] { "https://api.yammer.com/user_impersonation" };

            var cachedAccounts = oMicrosoftIdentityPublicClientApp.GetAccountsAsync().Result;
            var firstAccount = cachedAccounts.FirstOrDefault();

            try
            {
                if (firstAccount != null)
                {
                    authResult = oMicrosoftIdentityPublicClientApp.AcquireTokenSilent(scopes, firstAccount).ExecuteAsync().Result;
                    return authResult.AccessToken;
                }
                else
                {
                    throw new MsalUiRequiredException("401","No Cached User Account. Please Sign-in.");
                }
            }
            catch (MsalUiRequiredException ex)
            {
                System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");

                try
                {
                    authResult = oMicrosoftIdentityPublicClientApp.AcquireTokenInteractive(scopes)
                        .WithAccount(cachedAccounts.FirstOrDefault())
                        .WithPrompt(Prompt.SelectAccount)
                        .ExecuteAsync().Result;
                    return authResult.AccessToken;
                }
                catch (MsalException msalex)
                {
                    Console.WriteLine(msalex.Message);
                    throw;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
        }

Preparation Before Calling the Yammer File Upload API

Before going to the further steps, you should have a Yammer group ID and the Network ID of your Yammer Tenant.

For Getting Yammer Network ID using Rest API follow the method mentioned below:
How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token
For getting the Group ID use any of the following method
  • Manually from Old Yammer: Please copy the value of feedId from the address bar.
How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token
  • From the New Yammer, manually copy the value after the /group/ from the address bar.
How To Programmatically Upload Large Files to Yammer Using Rest API and AAD Token

Calling Yammer API to Upload Large Files

For uploading large files to Yammer we need to call three consecutive API calls to complete the process and the Uploaded files will be displayed in the Yammer group.

Following are the sequence of Calls:

  1. POST Call to https://filesng.yammer.com/v3/createUploadSession to create the upload session.
  2. PUT call to the session URL returned from the above method and upload the file bytes in multiple chunks (Each chunk should not exceed more than 4MB)
  3. Once all the chunks are uploaded in previous step make a POST call to https://filesng.yammer.com/v3/completeDirectUploadSession to mark the completion of the upload process. After this call Yammer will show the file in Files section of the Yammer Group.

In each of the above rest calls we will pass the AAD token in header of the call as following:

request.AddHeader("Authorization", "Bearer " + YammerBearerToken);
Create Upload Session:
private static YammerCreateFileUploadSessionResponse CreateYammerUploadSession(object oRequest, ref string YammerBearerToken)
        {
            var oMicrosoftIdentityPublicClientApp = MicrosoftIdentityClientTokenHelper.MicrosoftIdentityPublicClientApp;
            YammerCreateFileUploadSessionResponse response = new YammerCreateFileUploadSessionResponse();
            YammerBearerToken = YammerBearerToken.Replace(Environment.NewLine, "");
            try
            {

                Console.WriteLine("Calling API createUploadSession.");
                var host = "https://filesng.yammer.com";

                var client = new RestClient(host);
                var request = new RestRequest("/v3/createUploadSession", Method.POST);

                request.AddHeader("Authorization", "Bearer " + YammerBearerToken);
                request.AddHeader("Content-Type", "application/json; charset=UTF-8");
                request.AddJsonBody(JsonConvert.SerializeObject(oRequest));

                var webResponse = client.Execute(request);
                if (!webResponse.IsSuccessful)
                {
                    throw new WebException(webResponse.StatusDescription);
                }

                return JsonConvert.DeserializeObject<YammerCreateFileUploadSessionResponse>(webResponse.Content);
            }
            catch
            {
                throw;
            }

        }
Upload File by Session URL:
private static YammerUploadToSharepointResponse UploadFileBySession(string url, byte[] file)
        {
            int fragSize = 1024 * 1024 * 4;
            var arrayBatches = ByteArrayIntoBatches(file, fragSize);
            int start = 0;
            string response = "";

            foreach (var byteArray in arrayBatches)
            {
                int byteArrayLength = byteArray.Length;
                var contentRange = " bytes " + start + "-" + (start + (byteArrayLength - 1)) + "/" + file.Length;

                using (var client = new HttpClient())
                {
                    var content = new ByteArrayContent(byteArray);
                    //content.Headers.Add("Content-Length", byteArrayLength.ToProperString());
                    content.Headers.Add("Content-Range", contentRange);

                    response = client.PutAsync(url, content).Result.Content.ReadAsStringAsync().Result;
                }

                start = start + byteArrayLength;
            }
            return JsonConvert.DeserializeObject<YammerUploadToSharepointResponse>(response);
        }
        private static IEnumerable<byte[]> ByteArrayIntoBatches(byte[] bArray, int intBufforLengt)
        {
            int bArrayLenght = bArray.Length;
            byte[] bReturn = null;

            int i = 0;
            for (; bArrayLenght > (i + 1) * intBufforLengt; i++)
            {
                bReturn = new byte[intBufforLengt];
                Array.Copy(bArray, i * intBufforLengt, bReturn, 0, intBufforLengt);
                yield return bReturn;
            }

            int intBufforLeft = bArrayLenght - i * intBufforLengt;
            if (intBufforLeft > 0)
            {
                bReturn = new byte[intBufforLeft];
                Array.Copy(bArray, i * intBufforLengt, bReturn, 0, intBufforLeft);
                yield return bReturn;
            }
        }
Complete Upload Session:
private static YammerCompleteDirectUploadSessionResponse CompleteYammerUploadSession(object oRequest, ref string YammerBearerToken)
        {
            YammerCreateFileUploadSessionResponse response = new YammerCreateFileUploadSessionResponse();
            YammerBearerToken = YammerBearerToken.Replace(Environment.NewLine, "");
            var oMicrosoftIdentityPublicClientApp = MicrosoftIdentityClientTokenHelper.MicrosoftIdentityPublicClientApp;
            try
            {
                Console.WriteLine("Calling API completeDirectUploadSession.");
                var host = "https://filesng.yammer.com";

                var client = new RestClient(host);
                var request = new RestRequest("/v3/completeDirectUploadSession", Method.POST);

                request.AddHeader("Authorization", "Bearer " + YammerBearerToken);
                request.AddHeader("Content-Type", "application/json; charset=UTF-8");
                request.AddJsonBody(JsonConvert.SerializeObject(oRequest));

                var webResponse = client.Execute(request);
                if (!webResponse.IsSuccessful)
                {
                    throw new WebException(webResponse.StatusDescription);
                }
                return JsonConvert.DeserializeObject<YammerCompleteDirectUploadSessionResponse>(webResponse.Content);
            }
            
            catch
            {
                throw;
            }
        }

Final Step is to write our Main method which will generate AAD token, prepare the request objects and call the above created methods.

Main Method:
static void Main(string[] args)
        {
            string YammerNetworkID = ConfigurationManager.AppSettings["YammerNetworkID"].ToString();

            Console.WriteLine("Enter Yammer Group ID:");
            string groupID = Console.ReadLine();

            Console.WriteLine("Enter Full path of Local File in Drive:");
            string FileFullPathinLocalDrive = Console.ReadLine();

            string strFileName = Path.GetFileName(FileFullPathinLocalDrive);
            bool uploadSuccess = false;
            try
            {
                var oMicrosoftIdentityPublicClientApp = MicrosoftIdentityClientTokenHelper.MicrosoftIdentityPublicClientApp;
                var oSessionRequestReq = new
                {
                    filename = strFileName,
                    group_id = groupID,
                    network_id = int.Parse(YammerNetworkID),
                    is_all_company = false,
                    upload_job_id = Guid.NewGuid().ToString()
                };
                string YammerBearerToken = GetYammerBearerToken(oMicrosoftIdentityPublicClientApp);
                var createSessionResponse = CreateYammerUploadSession(oSessionRequestReq, ref YammerBearerToken);
                var uploadResponse = UploadFileBySession(createSessionResponse.url, System.IO.File.ReadAllBytes(FileFullPathinLocalDrive));
                var oYammerCompleteDirectUploadSessionRequest = new
                {
                    filename = createSessionResponse.filename,
                    group_id = groupID,
                    network_id = int.Parse(YammerNetworkID),
                    is_all_company = false,
                    is_new_file = true,
                    sharepoint_id = uploadResponse.id,
                    uploaded_file_id = createSessionResponse.uploaded_file_id,
                    uploaded_file_version_id = createSessionResponse.uploaded_file_version_id
                };
                var uploadedFile = CompleteYammerUploadSession(oYammerCompleteDirectUploadSessionRequest, ref YammerBearerToken);

                if (uploadedFile != null && !string.IsNullOrWhiteSpace(uploadedFile.web_url))
                {
                    uploadSuccess = true;
                }

                Console.WriteLine(uploadSuccess ? $"File uploaded to yammer and file url is {uploadedFile.web_url}" : $"Upload failed");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

Summary

Once all the above execution is complete you should be able to see the uploaded document in Files tab of the Yammer Group as following:

Learnings

This 2-parts article, demonstrates how to register Azure AD app, generate token using that to overcome the primary issue of limitation of Yammer official API for uploading large files.

1 comment

  1. Hello can you please provide code or redirect url for using web application
    I’m trying to set web and redirecuri to https://localhost/ it it doing nothing page is loding only on tork. Generate
    Can you please help me with web code

Leave a comment

Your email address will not be published. Required fields are marked *

Unravel The Complex
Stay Connected

Subscribe and receive the latest insights

Netwoven Inc. - Microsoft Solutions Partner

Get involved by tagging Netwoven experiences using our official hashtag #UnravelTheComplex