Uploading a file from MSDyn365BC as “multipart/form-data” message

Uploading a file from MSDyn365BC as “multipart/form-data” message

Yes, uploading a file from Microsoft Dynamics 365 Business Central to a Web API sounds easy because Microsoft AL provides all these new and cool objects like JsonObject, HttpRequest, HttpResponse and HttpClient.

But sometimes the devil is in the detail…
“How-to setup the corret HttpRequest message with all the needed HttpHeaders and how-to add the file content?”

Swagger “Web API” Definition

Good news, my Web API provide a Swagger definition. Now I see what is needed. Swagger-UI visualization display the API method “<host>/api/data”, the HTTP Verb “POST”, and the all parameters.

In simulation mode the Swagger-UI provide the first possibility to test the Web API call:

This part worked well with my file and filename!

Simulating the Web API call

Next step and a better way to set up and test the HTTP calls is a simulation. This allows to repeat the call with reduced clicking like selecting a file from web browser.

Postman or in my case the REST Client extension for Visual Studio Code are useful tools. You are able to specify in detail the required message and you can test this call as well.

Simulated “multipart/form-data” Http-Message

My Web API require a message with “multipart/form-data” and the Http specification for the call with REST Client extension is:

POST http://myapi.com/api/data HTTP/1.1
Content-Type: multipart/form-data; boundary="123456789"

--123456789
Content-Disposition: form-data; name="file"; filename="data.json"
Content-Type: application/json

{ "id": "my json file" }
--123456789--

In this example, the identifier “123456789” is used as a “boundary” to separate multiple content parts of the “multipart/form-data” message.

Further you can see, that the content of the boundary contains own message header definitions to describe the parameters as well as the content of the file.

HttpRequest with “multipart/form-data” in Microsoft AL

The previous created Http-Simulation provide all information about the needed commands in Microsoft AL.

Set up “form-data” content

First task is to set up file content and parameters between the boundary definition “123456789”. The “form-data” information contains boundary start, header with parameters, file-content, and boundary end.

I use these variables:

var tb: TextBuilder;
    content: HttpContent;

… and a TextBuilder (similar to StringBuilder in C#), because it is optimized to concatenate text. The needed code in Microsoft AL is:

// Boundary starts
tb.AppendLine('--123456789');
// Content Disposition Header Information and parameters like "filename"
tb.AppendLine('Content-Disposition: form-data; name="file"; filename="data.json"');
tb.AppendLine('Content-Type: application/json');
// Empty line required to separate the header information from payload
tb.AppendLine();
tb.AppendLine('{"id": "my json file" }');
// Boundary ends
tb.AppendLine('--123456789--');

… and the result as text:

--123456789
Content-Disposition: form-data; name="file"; filename="data.json"
Content-Type: application/json

{ "id": "my json file" }
--123456789--

Good warm up!

Set up the HttpMessage

My needed variables for this task in Micorsoft AL are:

var client: HttpClient;
    requestMessage: HttpRequestMessage;
    responseMessage: HttpResponseMessage;
    content: HttpContent;
    headers: HttpHeaders;

It needs the URL, HTTP Verb, Http-Header information, and the body content to set up the whole message:

// Setup the URL
requestMessage.SetRequestUri('https://myapi.com');

// Setup the HTTP Verb
requestMessage.Method := 'POST';

// Setup the Header Information like Authorization
requestMessage.GetHeaders(headers);
headers.Add('Authorization', 'Basic base64-user-password');

// Write the message content (multipart/form-data) into the message body
// (Note: This call will set some header information)
content.WriteFrom(tb.ToText());

// update the content header and define the boundary
content.GetHeaders(headers);
if headers.Contains('Content-Type') then headers.Remove('Content-Type');
headers.Add('Content-Type', 'multipart/form-data; boundary="123456789"');

Finally the HttpClient can send the composed HttpMessage and provide the result as HttpResponse (“responseMessage”):

client.Send(requestMessage, responseMessage);

Final Result

The whole method to upload a file as “multipart/form-data” to an Web API in Microsoft AL is:

procedure UploadJsonAsFile(json: Text; filename: Text): Integer
var
    client: HttpClient;
    requestMessage: HttpRequestMessage;
    responseMessage: HttpResponseMessage;
    content: HttpContent;
    headers: HttpHeaders;
    tb: TextBuilder;
    responseText: Text;
begin
    // Set up the message content for multipart/form-data
    tb.AppendLine('--123456789');
    tb.AppendLine(StrSubstNo('Content-Disposition: form-data; name="file"; filename="%1"', filename));
    tb.AppendLine('Content-Type: application/json');
    tb.AppendLine(); // Empty line required to separate the header information from payload
    tb.AppendLine(json);
    tb.AppendLine('--123456789--');

    // Write the content into HttpContent
    // (Note: This call will set some header information)
    content.WriteFrom(tb.ToText());
    
    // Get Content Headers
    content.GetHeaders(headers);

    // update the content header information and define the boundary    
    if headers.Contains('Content-Type') then headers.Remove('Content-Type');
    headers.Add('Content-Type', 'multipart/form-data; boundary="123456789"');

    // Setup the URL
    requestMessage.SetRequestUri('https://myapi.com');

    // Setup the HTTP Verb
    requestMessage.Method := 'POST';

    // Add some request headers like:
    requestMessage.GetHeaders(headers);
    headers.Add('Authorization', 'Basic base64-user-password');

    // Set the content
    requestMessage.Content := content;

    // Send the message
    client.Send(requestMessage, responseMessage);

    // Return the Status Code
    exit(responseMessage.HttpStatusCode());
end;

Conclusion

This example shows:

  • A simulation helps a lot to set up and test the Http-Message.
  • The Http-Simulation describes perfect the needed parts (header, body) of the Http-Message.

…and the Microsoft AL implementation looks “simple”.

If you know a better way to uploaded a file as “multipart/form-data” with Microsoft AL, Start Sharing your approach and experience with the community 😉

Share

Leave a Reply

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