Business central blog
Subscribe to blog and get news about new posts.

Universal http SendRequest() method. Multipart form data.

Today, I want to share an updated universal SendRequest method. Additionally, I'd like to show an example of working with multipart form data.

Universal SendRequest() method

In working with third-party APIs, you often have to send HTTP requests. For this, I almost always use a codeunit that I previously wrote. Most of the time, a text variable will suffice when you need to read a response from the server. However, sometimes the server's response will be a binary file, which could be an archive or a PDF document, etc. Therefore, in one of my latest iterations, I added a TempBlob codeunit as a return parameter to the main SendRequest() function. This way, the function supports returning both text and binary types of information.

procedure SendRequest(contentToSend: Variant; RequestMethod: enum "Http Request Type"; requestUri: Text; ContentType: Text; HttpTimeout: integer; var ResponseValue: Codeunit "Temp Blob"; DictionaryContentHeaders: Codeunit "Dictionary Wrapper"; DictionaryDefaultHeaders: Codeunit "Dictionary Wrapper")
var
    Client: HttpClient;
    Request: HttpRequestMessage;
    Response: HttpResponseMessage;
    ContentHeaders: HttpHeaders;
    Content: HttpContent;
    ErrorBodyContent: Text;
    TextContent: Text;
    InStreamContent: InStream;
    InStreamRepsonse: InStream;
    OutStreamResponse: OutStream;
    i: Integer;
    KeyVariant: Variant;
    ValueVariant: Variant;
    HasContent: Boolean;
begin
    case true of
        contentToSend.IsText():
            begin
                TextContent := contentToSend;
                if TextContent <> '' then begin
                    Content.WriteFrom(TextContent);
                    HasContent := true;
                end;
            end;
        contentToSend.IsInStream():
            begin
                InStreamContent := contentToSend;
                Content.WriteFrom(InStreamContent);
                HasContent := true;
            end;
        else
            Error(UnsupportedContentToSendErr);
    end;

    if HasContent then
        Request.Content := Content;

    if ContentType <> '' then begin
        ContentHeaders.Clear();
        Request.Content.GetHeaders(ContentHeaders);
        if ContentHeaders.Contains(ContentTypeKeyLbl) then
            ContentHeaders.Remove(ContentTypeKeyLbl);

        ContentHeaders.Add(ContentTypeKeyLbl, ContentType);
    end;

    for i := 0 to DictionaryContentHeaders.Count() do
        if DictionaryContentHeaders.TryGetKeyValue(i, KeyVariant, ValueVariant) then
            ContentHeaders.Add(Format(KeyVariant), Format(ValueVariant));

    Request.SetRequestUri(requestUri);
    Request.Method := Format(RequestMethod);

    for i := 0 to DictionaryDefaultHeaders.Count() do
        if DictionaryDefaultHeaders.TryGetKeyValue(i, KeyVariant, ValueVariant) then
            Client.DefaultRequestHeaders.Add(Format(KeyVariant), Format(ValueVariant));

    if HttpTimeout <> 0 then
        Client.Timeout(HttpTimeout);

    Client.Send(Request, Response);

    ResponseValue.CreateInStream(InStreamRepsonse);
    ResponseValue.CreateOutStream(OutStreamResponse);

    Response.Content().ReadAs(InStreamRepsonse);
    if not Response.IsSuccessStatusCode() then begin
        Response.Content().ReadAs(ErrorBodyContent);
        Error(RequestErr, Response.HttpStatusCode(), ErrorBodyContent);
    end;

    CopyStream(OutStreamResponse, InStreamRepsonse); //Save data to TempBlob
end;

var
    RequestErr: Label 'Request failed with HTTP Code:: %1 Request Body:: %2', Comment = '%1 = HttpCode, %2 = RequestBody';
    UnsupportedContentToSendErr: Label 'Unsuportted content to sned.';
    ContentTypeKeyLbl: Label 'Content-Type', Locked = true;

Multipart form data

First of all, I want to note that Microsoft has recently updated the documentation for HTTP data types. I recommend you take a look at this and that. I want to focus on multipart form data and how we can work with it in Business Central.
Let's delve into what multipart content-type is:
https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
Also, check about multipart/form-data:
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
In summary, multipart/form-data is used to send one or multiple files in a request, or to send a single file in parts. I will provide an example of sending multiple files using multipart/form-data content type in Business Central:

var
    APIMgt: Codeunit "FOD API Mgt";
    ResponseData: Codeunit "Temp Blob";
    FileInStream: InStream;
    FormData: TextBuilder;
    Boundary: Text;
    TextBuffer: Text;
begin
    Boundary := Format(CreateGuid(), 0, 3);

    FormData.AppendLine(StrSubstNo('--%1', Boundary));
    FormData.AppendLine('Content-Disposition: form-data; name=foo1;');
    FormData.AppendLine('Content-Type: text/plain');
    FormData.AppendLine('');
    FormData.AppendLine('bar1');

    FormData.AppendLine(StrSubstNo('--%1', Boundary));
    FormData.AppendLine('Content-Disposition: form-data; name=foo2;');
    FormData.AppendLine('Content-Type: text/plain');
    FormData.AppendLine('');
    FormData.AppendLine('bar2');

    FormData.AppendLine(StrSubstNo('--%1--', Boundary));
    FormData.AppendLine('');

    APIMgt.SendRequest(FormData.ToText(), Enum::"Http Request Type"::POST, 'https://postman-echo.com/post', StrSubstNo('multipart/form-data; boundary=%1', Boundary), ResponseData);

Source Code

September 4, 2023