Document management is very vital for enterprises today for storing and retrieval of documents. OneDrive for Business with Dynamics 365 Business Central allow to do that, and in today’s post, we’ll talk about OneDrive for Business within Dynamics 365 Business central, how a file can be uploaded into One Drive from Dynamics 365 Business central.

It’s been a while since Business central started supporting Rest APIs. These days, Power-Automate and Azure logic apps are making it easier to integrate with Business central. Business Central has a lot of potentials to integrate with office 365 products. I’ve read the blogs discussing how it can be achieved using Azure functions or power automate and I wanted to do this the pure AL way!

Since there are lots of other blogs where App Registration in Azure AD,
API Permissions are shared in a detailed manner, I’m going to skip to the part
where we get to the see how this integration is achieved.

For Starters, we are going to need a table to capture the One Drive credentials and Drive Folder Name. For this specific exercise, am capturing the Client ID, Client Secret, Directory ID, Drive ID (One Drive), Folder Name in the table OneDrive Setup.
Then we need the option to select a file to upload. since File management is not accessible in BC Cloud, we’ll use the UploadIntoStream. Using this, we can get the filename using a different function. We’ll also be needing the length of the file in bytes. For this, I’m using Tempblob.Length function. we’ll be needing the File Length when we upload the file. we’ll get to that part soon.

        OnedriveSetup.Get();
        TempBlob.CreateInStream(InStr);
        UploadIntoStream('Import', '', ' All Files (*.*)|*.*', FilePath, InStr);
        FileName := GetFileType(FilePath);
        IStream := InStr;        
        LengthOfFile := TempBlob.Length();
        TempBlob.CreateInStream(InStr);
        IStream := InStr;
        FileName := GetFileType(FilePath);
procedure GetFileType(pFilename: Text): Text;
    begin
        if StrPos(pFilename, '\') = 0 then
            exit(pFilename)
        else begin
            FilenamePos := StrLen(pFilename);
            while (pFilename[FilenamePos] <> '\') or (FilenamePos < 1) do
                FilenamePos -= 1;

            if FilenamePos = 0 then
                exit('');

            exit(CopyStr(pFilename, FilenamePos + 1, StrLen(pFilename)));
        end;
    end;

Next, we create a function to retrieve the bearer token which will enable us to upload the file. In this exercise, am using the Content type x-www-form-urlencoded. we use the POST method to get the Bearer Token. Then, we parse the HTTP response content to a text variable, which is then read into a JSON Object. from there, getting the access token is pretty straight forward. am adding the function right below for those who need to try it.

procedure GetBearerToken(): Text
    var
        lClient: HttpClient;
        lResponse: HttpResponseMessage;
        lContent: HttpContent;
        lHeaders: HttpHeaders;
        lUrl: Text;
        lJsonObj: JsonObject;
        lJsonToken: JsonToken;
        Token: text;
        lClientID: text[250];
        lSecret: text[250];
        BaseTxt: Text[1024];
        APITokenLocal: Text;
    begin

        lUrl := 'https://login.microsoftonline.com/' + OnedriveSetup."Directory ID" + '/oauth2/v2.0/token';
        lClientID := Onedrivesetup."Client ID";
        lSecret := OnedriveSetup."Client Secret";
        BaseTxt := 'grant_type=client_credentials&client_id=' + lClientID + '&client_secret=' + lSecret + '&scope=https://graph.microsoft.com/.default';
        lContent.Clear();
        lContent.WriteFrom(BaseTxt);
        lHeaders.Clear();
        lContent.GetHeaders(lHeaders);
        lHeaders.Remove('Content-Type');
        lHeaders.Add('Content-Type', 'application/x-www-form-urlencoded');
        lContent.GetHeaders(lHeaders);
        if lClient.Post(lUrl, lContent, lResponse) then begin
            lResponse.Content().ReadAs(Token);
            lJsonObj.ReadFrom(Token);
            lJsonObj.Get('access_token', lJsonToken);
            lJsonToken.WriteTo(APITokenLocal);
            APITokenLocal := DelChr(APITokenLocal, '=', '"');
            Exit(APITokenLocal);
        end
        else
            error('API Token Request failed');
    end;

From this point on, things get interesting. The next step towards uploading files from business central would be to create an upload URL. for this, we create another function. This function will use a Post method to create an upload URL. I know that the syntax from Microsoft documentation of this URL requires a folder ID from one drive, but I’ve simply passed the folder Name instead. trust me, this works. if the Folder Name does not exist, then it will create one for you! Retrieving the Upload URL is done in a similar way we did with the bearer token. The function is as below:

Procedure CreateUploadURL(FileName: Text; BearerToken: Text): Text
begin
lUrl := 'https://graph.microsoft.com/v1.0/drives/' + OnedriveSetup."Drive ID" + '/root:/' + FolderName + '/' + FileName + ':/createUploadSession';
        Bearer := 'Bearer ' + BearerToken;
        lHeaders.Clear();
        lContent.GetHeaders(lHeaders);
        lHeaders.Remove('Content-Type');
        lHeaders.Add('Content-Type', 'application/json');
        lreqHeaders := lClient.DefaultRequestHeaders();
        lreqHeaders.Add('Authorization', Bearer);
        lreqHeaders.Remove('Accept');
        lreqHeaders.Add('Accept', 'application/json');
        lRequest.GetHeaders(lReqHeaders);
        lContent.GetHeaders(lHeaders);
        lRequest.Method := 'POST';
        lRequest.SetRequestUri(lUrl);
        lRequest.GetHeaders(lReqHeaders);
        Clear(BaseTxt);
        if lClient.Send(lRequest, lResponse) then begin
            lResponse.Content().ReadAs(BaseTxt);
            lJsonObj.ReadFrom(BaseTxt);
            if lResponse.IsSuccessStatusCode() then begin
                lJsonObj.Get('uploadUrl', lJsonToken);
                lJsonToken.WriteTo(WebUrl);
                WebUrl := DelChr(WebUrl, '=', '"');
                exit(WebUrl);
            end else
                Error(Text50001Lbl);
        end;
    end;

Now, we get to upload the file using the upload URL we just created. This is the part where the length of the uploaded file which we stored in a variable, in the beginning, comes to play. We need the length of the file in bytes to be passed in the header Content-Length. for this exercise, am uploading the file in one stretch instead of breaking the file down into multiple parts. Thus, I have passed the Full Length of the file in Content-Range. I’ve used Content-Type “octet-Stream” so that it will support all file types. Usually, the file content is converted into Base64 string and then uploaded, But I have passed the stream directly into the content. this worked out pretty well. The upload function is as below:

procedure PutFileUsingUploadSession(UploadUrl: Text; BearerToken: Text)
begin
        lUrl := UploadUrl;
        Bearer := 'Bearer ' + BearerToken;
        lHeaders.Clear();
        lContent.GetHeaders(lHeaders);
        lHeaders.Remove('Content-Type');
        lHeaders.Add('Content-Type', 'application/octet-stream');
        lHeaders.Add('Content-Length', format(LengthOfFile));
        lHeaders.Add('Content-Range', 'bytes 0-' + format(LengthofFile - 1) + '/' + format(LengthOfFile));
        lreqHeaders := lClient.DefaultRequestHeaders();
        lreqHeaders.Add('Authorization', Bearer);
        lRequest.GetHeaders(lReqHeaders);
        lContent.WriteFrom(InStr);
        lContent.GetHeaders(lHeaders);
        lRequest.Method := 'PUT';
        lRequest.SetRequestUri(lUrl);
        lRequest.GetHeaders(lReqHeaders);
        lRequest.Content(lContent);
        Clear(BaseTxt);
        if lClient.Send(lRequest, lResponse) then begin
            lResponse.Content().ReadAs(BaseTxt);
            lJsonObj.ReadFrom(BaseTxt);
            if lResponse.IsSuccessStatusCode() then begin
                lJsonObj.Get('webUrl', lJsonToken);
                lJsonToken.WriteTo(WebUrl);
                WebUrl := DelChr(WebUrl, '=', '"');
		//At this point, the file is uploaded. The WebUrl is retrieved so that you can use it to store it as per your requirement
            end else
                Error(Text50001Lbl);
        end;
    end;

So far, I have tested this out with various file-types such as PDF, JPEG, PNG, PPT, XLSX, DOCX, MP4, etc. This code works for files within 40 MB. in order to upload large files, we need to break down into multiple recurrences of the last function. But that’s content for another post.

Hope this helps anyone looking out to uploading files to one-drive using pure AL code. If you still have the questions or learn more about our services then feel  free to reach out to us at sales@cetastech.com

*This blog was originally posted here