The Building Blocks of AL’s HTTP Integration in Dynamics 365 BC

Guidelines for Partners

In the modern era of digital business, seamless connectivity and data exchange are paramount. This is where HTTP integration plays a pivotal role in Dynamics 365 Business Central. HTTP integration is essentially the ability of this ERP system to communicate and exchange data with external web services, applications, and APIs over the Hypertext Transfer Protocol (HTTP). In this blog post, you will be introduced to HTTP data types and explore some basic examples of uses of HTTP integration within Dynamics 365 Business Central.

 

AL’s capability to interact with HTTP services is a set of essential classes. These classes are an embodiment of the robust System.Net.Http classes from the .NET Framework, carefully tailored to fit the Dynamics 365 Business Central environment. The key players in this ensemble include:

  • HttpClient: Your go-to tool for initiating HTTP requests and managing responses.
  • HttpContent: Facilitating content manipulation within HTTP requests and responses.
  • HttpHeaders: Ensuring precise control over HTTP headers for efficient communication.
  • HttpRequestMessage: Crafting and customizing HTTP requests with precision.
  • HttpResponseMessage: Handling and processing HTTP responses seamlessly.

 

It’s worth noting that all HTTP types in AL are reference types, distinguishing them from value types commonly used in other contexts. This distinction unlocks an array of possibilities in your AL code, granting you the power to communicate effectively with external services, synchronize data, and automate critical processes.

REST services can be interacted with via HTTP requests, and to initiate such requests, it is essential to employ an HTTP verb. Among the commonly utilized verbs in REST services, the following stand out:

  • GET (utilized for data retrieval)
  • POST (employed for data creation)
  • PUT (applied to update or replace existing data)
  • PATCH (used to modify or update data)
  • DELETE (deployed for data deletion)

 

HttpClient class

This table provides a quick reference to the methods and their respective descriptions in the HttpClient class.

Method Description
DefaultRequestHeaders() Gets the default request headers that should be sent with each request.
SetBaseAddress(Uri) Sets the base address of the URI that is used when you are sending requests.
Get(Uri, HttpResponseMessage) Sends a GET request to get the resource identified by the request URI.
Post(Uri, HttpContent, HttpResponseMessage) Sends a POST request to the specified URI.
Put(Uri, HttpContent, HttpResponseMessage) Sends a PUT request to the specified URI.
Delete(Uri, HttpResponseMessage) Sends a DELETE request to the specified URI.
Send(HttpRequestMessage, HttpResponseMessage) Sends an HTTP request. In the HttpRequestMessage variable, you must provide the URI and the verb to use.
Clear() Sets the HttpClient variable to the default value.
Timeout() Gets or sets the duration in seconds to wait before the request times out. Timeout is an integer value.
AddCertificate(Certificate, Password) Adds a certificate to the HttpClient class. You need to provide a base64 encoded certificate.

 

When working with URIs, there are two distinct approaches at your disposal. The initial method entails fetching data from the specified URL.

Subsequently, you can employ the ‘SetBaseAddress’ function when utilizing the ‘HttpClient’ class to conduct multiple requests:

 

var
    customClient: HttpClient;
    customResponse: HttpResponseMessage;
begin
    customClient.SetBaseAddress('https://jsonplaceholder.typicode.com/');
    customClient.Get('posts', customResponse);
end;

 

On the other hand, the second approach is ideal when you require data retrieval just once. In such cases, you can directly include the entire URI within the ‘Get’ function:

 

var
    singleRequestClient: HttpClient;
    singleResponse: HttpResponseMessage;
begin
    singleRequestClient.Get('https://jsonplaceholder.typicode.com/posts', singleResponse);
end;

These approaches provide flexibility depending on your specific use case and the number of requests you need to make.

 

HttpHeaders class

Within the HttpHeaders class lies a repository of headers and their associated values. HttpHeaders serve as crucial components transmitted with each request and response. They fulfill the role of conveying supplementary details regarding the request or specifying the desired formatting for the response.

Among the noteworthy HttpHeaders in use are:

  1. Authorization: Employed to transmit authentication credentials.
  2. Content-Type: Specifies the media type of the request body.
  3. User-Agent: Contains the user agent string, often indicating the browser’s identity.
  4. Accept-Charset: Determines which character sets are deemed acceptable.

To access HttpHeaders, one can utilize the ‘DefaultRequestHeaders’ property provided by the HttpClient class.

This table provides a quick reference to the methods and their respective descriptions for managing headers within the HttpHeaders class.

Method Description
Add(Key, Value) Sets the provided value for the provided header name.
Clear() Sets the HttpHeaders variable to the default value.
Contains(Key) Check if an HttpHeaders variable contains a property with the given key.
GetValues(Key, Array of Text) Retrieves the values for the specified key and stores them in an array of text.
Remove(Key) Removes the key and the related values from the HttpHeaders object.

 

HttpResponseMessage class

The HttpResponseMessage class serves as a representation of an HTTP response message. Such a response message materializes as the outcome of an HTTP operation, which could include actions like Get, Post, Put, or Delete. This response is encapsulated within the HttpResponseMessage parameter.

This table provides a quick reference to the methods and their respective descriptions for working with the HttpResponseMessage class in the context of HTTP responses.

Method Description
Content() Retrieves the contents (HttpContent) of the HTTP response.
Headers() Retrieves the HTTP headers of the HTTP response.
HttpStatusCode() Retrieves the status code of the HTTP response.
IsSuccessStatusCode() Indicates whether the HTTP response was successful (returns a boolean value).
ReasonPhrase() Retrieves the reason phrase that typically accompanies the status code, offering additional context for failed requests.

 

HttpContent class

The HttpContent class serves as a container for both the body and content headers in an HTTP message. It plays a dual role, serving as the payload for sending data in an HTTP request and as the body of an HTTP response. You can access and manipulate the content using the ‘Content’ property, which is available on the HttpRequestMessage when crafting a request and on the HttpResponseMessage when dealing with a response.

This table provides a quick reference to the methods and their respective descriptions for managing content within the HttpContent class.

Method Description
Clear() Resets the HttpContent object to its default value.
GetHeaders(HttpHeaders) Retrieves the HTTP headers associated with the content and stores them in the HttpHeaders parameter.
ReadAs(Result) Reads the content into the provided text or stream, with the result being of type Text or InStream.
WriteFrom(Value) Sets the HttpContent variable to the provided text or stream.

 

HttpRequestMessage class

The HttpRequestMessage class represents an HTTP request message. A request message is the class that is used to send a request.

This table provides a quick reference to the methods and their respective descriptions for working with the HttpRequestMessage class when creating HTTP requests.

Method Description
Content() Gets or sets the contents of the HTTP request.
GetRequestUri() Retrieves the URI used for the HTTP request.
Method() Gets or sets the HTTP method type for the request. You must provide an HTTP verb for this.
SetRequestUri(RequestUri) Sets the URI to be used for the HTTP request.

 

Now, let’s explore some examples. We’ll use what’s called a ‘Fake API,’ which provides simulated data whenever needed. Here, I’ve created a page for testing purposes with several examples.

 

page 50100 "HTML_TEST"
{
    ApplicationArea = All;
    Caption = 'HTML TEST';
    UsageCategory = Administration;



    layout
    {
        area(Content)
        {
            field(UserID; UserID)
            {
                ApplicationArea = All;
                Caption = 'User ID';
            }
        }
    }



    actions
    {
        area(Processing)
        {
            action(CreatePosts)
            {
                ApplicationArea = All;
                Caption = 'Create Post';
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;



                trigger OnAction()
                begin
                    // Action to create a post using an HTML call.
                    CreatePost();
                end;
            }
            action(GetUser)
            {
                ApplicationArea = All;
                Caption = 'Get User';
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;



                trigger OnAction()
                begin
                    // Action to get user information using an HTML call.
                    GetUserInformation(UserID);
                end;
            }
            action(Delete)
            {
                ApplicationArea = All;
                Caption = 'Delete User';
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;



                trigger OnAction()
                begin
                    // Action to delete a user using an HTML call.
                    DeleteUser(UserID);
                end;
            }
            action(Update)
            {
                ApplicationArea = All;
                Caption = 'Update User';
                Promoted = true;
                PromotedCategory = Process;
                PromotedIsBig = true;



                trigger OnAction()
                begin
                    // Action to update user information using an HTML call.
                    UpdateUser(UserID);
                end;
            }
        }
    }



    var
        UserID: Integer;



    procedure GetUserInformation(UserNumber: Integer)
    var
        Client: HttpClient;
        ResponseMessage: HttpResponseMessage;
        ResponseString: Text;
        Jtoken: JsonToken;
        Jtoken2: JsonToken;
        JObject: JsonObject;
    begin
        // Send an HTTP GET request to the web service with the specified UserNumber.
        if not Client.Get(StrSubstNo('https://jsonplaceholder.typicode.com/users/%1',
                          UserNumber), ResponseMessage) then
            Error('The call to the web service failed.');



        // Check if the response indicates success.
        if not ResponseMessage.IsSuccessStatusCode() then
            Error('The web service returned an error message:\\' +
                  'Status code: ' + Format(ResponseMessage.HttpStatusCode()) +
                  'Description: ' + ResponseMessage.ReasonPhrase());



        // Read the response content as a text.
        ResponseMessage.Content().ReadAs(ResponseString);



        // Parse the JSON response.
        if not Jtoken.ReadFrom(ResponseString) then
            Error('Invalid JSON document.');



        // Ensure the JSON response is an object.
        if not Jtoken.IsObject() then
            Error('Expected a JSON object.');



        // Convert the value in a JsonToken to a JsonObject.
        JObject := Jtoken.AsObject();



        // Get the 'name' property from the JSON response.
        if not JObject.Get('name', Jtoken2) then
            Error('Value for key name not found.');



        // Display the 'name' value.
        if not Jtoken2.IsValue then
            Error('Expected a JSON value.');



        Message(Jtoken2.AsValue().AsText());
    end;



    procedure CreatePost()
    var
        Client: HttpClient;
        Content: HttpContent;
        ResponseMessage: HttpResponseMessage;
        ResponseString: Text;
        JObject: JsonObject;
        JsonText: Text;
    begin
        // Add data to the JSON object.
        JObject.Add('userId', 1);
        JObject.Add('body', 'foo');
        JObject.Add('title', 'bar');



        // Convert the JSON object to text.
        JObject.WriteTo(JsonText);



        // Create HttpContent from the JSON text.
        Content.WriteFrom(JsonText);



        // Send an HTTP POST request to create a post.
        if not Client.Post('https://jsonplaceholder.typicode.com/posts', Content,
                           ResponseMessage) then
            Error('The call to the web service failed.');



        // Check if the response indicates success.
        if not ResponseMessage.IsSuccessStatusCode() then
            Error('The web service returned an error message:\\' +
                    'Status code: ' + Format(ResponseMessage.HttpStatusCode()) +
                    'Description: ' + ResponseMessage.ReasonPhrase());



        // Read the response content as text and display it.
        ResponseMessage.Content().ReadAs(ResponseString);
        Message(ResponseString);
    end;



    procedure DeleteUser(UserNumber: Integer)
    var
        Client: HttpClient;
        Content: HttpContent;
        ResponseMessage: HttpResponseMessage;
        ResponseString: Text;
    begin
        // Send an HTTP DELETE request to the web service with the specified UserNumber.
        if not Client.Delete(StrSubstNo('https://jsonplaceholder.typicode.com/users/%1',
                          UserNumber), ResponseMessage) then
            Error('The call to the web service failed.');



        // Check if the response indicates success.
        if not ResponseMessage.IsSuccessStatusCode() then
            Error('The web service returned an error message:\\' +
                    'Status code: ' + Format(ResponseMessage.HttpStatusCode()) +
                    'Description: ' + ResponseMessage.ReasonPhrase());



        // Read the response phrase and display it.
        Message(ResponseMessage.ReasonPhrase);
    end;



    procedure UpdateUser(UserNumber: Integer)
    var
        Client: HttpClient;
        Content: HttpContent;
        ResponseMessage: HttpResponseMessage;
        ResponseString: Text;
        JObject: JsonObject;
        JsonText: Text;
    begin
        // Add data to the JSON object.
        JObject.Add('id', 100);
        JObject.Add('name', 'TEST NAME');
        JObject.Add('username', 'TEST USERNAME');



        // Convert the JSON object to text.
        JObject.WriteTo(JsonText);



        // Create HttpContent from the JSON text.
        Content.WriteFrom(JsonText);



        // Send an HTTP PUT request to update user information.
        if not Client.Put(StrSubstNo('https://jsonplaceholder.typicode.com/users/%1',
                          UserNumber), Content, ResponseMessage) then
            Error('The call to the web service failed.');



        // Check if the response indicates success.
        if not ResponseMessage.IsSuccessStatusCode() then
            Error('The web service returned an error message:\\' +
                    'Status code: ' + Format(ResponseMessage.HttpStatusCode()) +
                    'Description: ' + ResponseMessage.ReasonPhrase());



        // Read the response phrase and display it.
        Message(ResponseMessage.ReasonPhrase);
    end;
}


By using these procedures, resources will not actually be updated on the server, but they will be simulated as if they were, allowing us to test these methods successfully.

Firstly, we will create a new post. After a JSON object is created and filled with the required information, the corresponding request will send this information to the server. If something goes wrong, a response message will handle the errors. If everything is in order, we will receive a response string containing ‘id=101.’ This indicates that we have received information about the newly created post, assigned the ID 101.

HTTP Integration

 

The ‘Get User’ action will retrieve a user’s name with the same ID as the one we selected in the ‘User ID’ field. After that, the ‘HttpClient’ will initiate a ‘GET’ request to obtain the user information we need, which we will extract from the ‘ResponseMessage’ using ‘JToken’ and ‘JObject’.

HTTP Integration 

 

HTTP Integration

 

The ‘Delete’ request operates similarly to other requests. We specify the path to the ‘Users’ page and pass the selected ‘User ID’ as a variable. Once the request is initialized, we verify if the response indicates a successful operation.

HTTP Integration

 

Finally, the ‘UpdateUser’ procedure demonstrates how to update or replace existing data on the server. We should create a ‘JObject,’ adding and filling certain keys with the values we want to replace in user data. If everything goes smoothly, we should receive a response message with the reason phrase ‘OK’.

Conclusion

Here, we’ve offered a simple overview of HTTP integration within Dynamics 365 Business Central. We covered main “AL” tools for “System.Net.Http” wrappers, while also providing basic examples to understand the idea behind HTTP requests. Keep an eye out for our upcoming blog, where we’ll guide you through the process of configuring and establishing connections with the Business Central API. We’ll also demonstrate how to use Postman to test real requests effectively.