Automated file upload to Business Central Cloud

Since our community area has been discussing for some time how to upload files to the cloud without user action to Business Central, I took a closer look at this topic.

Starting point

You have a cloud version of Business Central and want to read one or more files automatically without the user having to select that file through a dialog each time.

Problem

You do not have access to the server that is used in the background. In addition, only a few .net functions are available directly in the AL programming environment and unfortunately the necessary functions and commands are not available for file processing.

Solution

Since you cannot place the file(s) directly on the server running Business Central as a cloud version, you have to place the files on another server that is accessible from outside. Of course, there are many ways to do this: a dedicated server, Sharepoint, AWS, Azure,…

After we’re in a Microsoft environment, I chose Azure and azure file services.

Furthermore, I need a replacement for the missing .net functions. This is where I chose Azure Functions.

So the process looks like this in brief:

  • The file(s) are loaded to Azure File Storage
  • An Azure Function reads the file and returns the content
  • AL Code calls this Azure Function and processes the result

Conditions

In order to implement this example, the following is required:

  • Azure Account
  • Visual Studio Code with AL Extension and Azure Functions Extension
  • Business Central Client (Cloud or OnPremises)
  • Azure Functions Basic Knowledge
  • AL programming skills

For those who haven’t looked at Azure Functions in detail, here’s a handy introduction:

The whole video training can be found here:

Azure Functions in Business Central

The example in detail

I created an Azur File Storage called “l4d365” on my Azure account. There is also a file share named “l4d365”. Within this file share there is a folder called “Files”. And within this folder I uploaded a file called “test.txt” for example. This is a simple text file with the content “This is a test, it worked, Juhuu!”.

Now it’s time to create an Azure Function. It is an http trigger function and it looks like this:

using system;
using System.IO;
using system.Threading.Tasks;
Using Microsoft.AspNetCore.Mvc;
Using Microsoft.Azure.WebJobs;
Using Microsoft.Azure.WebJobs.Extensions.Http;
Using Microsoft.AspNetCore.Http;
Using Microsoft.Extensions.Logging;
Using Microsoft.Azure.Storage;
Using Microsoft.Azure.Storage.File; 
Using Newtonsoft.Json;

namespace DownLoadFile
{
   public static class DownloadFile
   {
     [FunctionName("DownloadFile")]  
      public static async Task<IActionResult> Run(
           [HttpTrigger(AuthorizationLevel.Anonymous, "get","post", Route = null 
           )]HttpRequest req,ILogger log)
      {
           log.LogInformation("C- HTTP trigger function processed a request.");

           string connectionString = req.Query["connectionString"]; 
           string shareName = req.Query["shareName"];
           string dirName = req.Query["dirName"];
           string fileName = req.Quer["fileName"]y;

           string requestBody = await new StreamReader(req.body).ReadToEndAsync();
           dynamic data = JsonConvert.DeserializeObject(requestBody); 
           connectionString = connectionString ?? Data?.connectionString;   
           shareName = shareName ?? data?.shareName; 
           dirName = dirName ?? data?.dirName;
           fileName = fileName ?? data?.fileName;

           if (string.IsNullOrEmpty(connectionString)) 
           {
              return new BadRequestObjectResult("This HTTP triggered function 
              "I'm not.Pass a name in the query 
              string or in the request body for a personalized response.");
           }
           CloudStorageAccount storageAccount = CloudStorageAccount.Parse
              (connectionString);
           CloudFileClient fileClient = storageAccount.CreateCloudFileClient(); 
           CloudFileShare share = fileClient.GetShareReference(shareName);
           if (share.Exists ())
           {
              CloudFileDirectory rootDir = share.GetRootDirectoryReference();
              CloudFileDirectory sampleDir = rootDir.GetDirectoryReference(dirName);
              
              if (sampleDir.Exists())
              {
                 CloudFile file = sampleDir.GetFileReference(fileName);

                 if (file.Exists ())
                 {
                    return new OkObjectResult(file.DownloadText()); 
                 }
              }
           }
           return new BadRequestObjectResult("Error in Code");
       }
   }
}

Finally, I’ll create the AL function that calls this Azure Function:

page 70100 ImportFileFromAzure
{
    PageType = Card;
    ApplicationArea = All;
    UsageCategory = Administration;
    
    Layout
    {
    }

    actions
    {
        area(Processing)
        {
            action(ActionName)
            {
                ApplicationArea = All;

                trigger OnAction()
                Var
                    client: HttpClient;
                    response: HttpResponseMessage;
                    request: HttpRequestMessage;
                    content: HttpContent;
                    headers: HttpHeaders;
                    TempBlob: Temp Blob codeunit;
                    InStr: InStream;
                    fileContent : text;
                Begin
                    content.WriteFrom('{"connectionString": "DefaultEndpointsProtoco
                     l=https; AccountName=l4d365; AccountKey=XYZ;
                     EndpointSuffix=core.windows.net","shareName": "l4d365",
                     "dirName": "Files","fileName": "Test.txt"}' );
                    
                    content.GetHeaders(headers);
                    headers.Clear();
                    headers.Add('Content-Type', 'application/json');

                    request.Content := content;
                    request.SetRequestUri('https://azurefunctionappURL');
                    request.Method := 'POST';

                    if client.Send(request, response) then begin
                        response.Content().ReadAs(fileContent);
                        message(fileContent);
                    end;
                end;
            }
        }
    }
}

If everything worked out, business central will receive the message that outputs the file contents.

A detailed video training guide can be found on our website:

File upload to Business Central using Azure Functions (German)

This was a simple example that can now be expanded in a variety of ways, e.g.:

  • Read multiple files
  • Read binary data such as images instead of text
  • Hang files directly into the incoming documents

So it remains exciting!

Leave a Reply

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