The HTTPClient available to us from within AL is a very useful feature in this modern time full of APIs, but it is fair to say that the documentation for it is somewhat lacking. In this post I intend to explain how I’ve used it and hopefully reveal a few of it’s secrets.
Whilst this post is primarily about the HTTPClient, I could not explain it without also delving into it’s brethren datatypes, namely HTTPHeaders, HTTPContent, HTTPResponseMessage and HTTPRequestMessage. I’ll firstly explain what each of these data types are for, and how they combine to form a whole HTTP request, then follow up with an example of how I utilise them in one handy wrapper function.
Firstly, is the HTTPClient itself. This is the main engine of our integration and handles the actual communication with the API or webservice. In fairly loose terms, it allows for us to initiate a Read (Get), Insert (Post), Modify (Put/Patch) or Delete (Delete) request to the webservice/API we wish to interact with.
For all of these requests, we need to provide the URL we are making the request to, and it is expected that all of these methods will provide some sort of response, and that is where the HTTPResponseMessage comes into play. By giving the call a HTTPResponseMessage variable, we will then be able to read & interact with that variable to understand the data that is passed back. This would primarily be in the form of the response content (JSON in my case, but other formats may be passed) or a HTTP Status code.
ResponseMessage.Content().ReadAs(ResponseText);
If we need to pass some data as part of the request, then we would use a Post, Put or Patch request – all of these methods require that we pass some data and this is where the HTTPContent data type comes into play.
In short, the HTTPContent is the body of the request you’re trying to send. For the most part I simply load this from a text variable containing the message I want to send.
RequestContent.WriteFrom(payload);
You’ll also almost certainly need to provide some sort of authentication as part of your request. This is handled within the HTTPHeaders datatype. For my scenario basic authentication is fine, so that is what we’ll discuss here.
For this I’ll inherit my headers from the client, and then add a key/value pair into the headers
RequestHeaders := Client.DefaultRequestHeaders();
RequestHeaders.Add('Authorization',CreateBasicAuthHeader(Username,Password));
The value for this authentication header is created by a function which converts the username & password to a base64 string
procedure CreateBasicAuthHeader(UserName:Text;Password:Text):Text var
TempBlob: Record TempBlob;
begin
TempBlob.WriteAsText(StrSubstNo('%1:%2', UserName, Password), TextEncoding::UTF8);
exit(StrSubstNo('Basic %1', TempBlob.ToBase64String()));
end;
If you can use the GET, POST, PUT and DELETE methods provided as part of the HTTPClient datatype for your request, then it’s pretty plain sailing and the above should be enough information to get you started.
However, I’ve found these to be somewhat limited in their use when it comes to the POST and PUT methods.
For these, I needed to be able to send more information than just the content and request headers, this is where the HTTPRequestMessage and the ‘SEND’ method is used.
This allows me to specify the content type I’m sending, as well as utilise other HTTP methods such as a ‘Patch’ rather than ‘Put’, which is required by some API/Webservices.
To specify the content type, I’ll utilise another HTTPHeader variable, but this time inherit from the HTTPContent, and then add the header key & value specifying my content type:
RequestContent.GetHeaders(contentHeaders);
contentHeaders.Clear();
contentHeaders.Add('Content-Type', 'application/json');
This can then be used with the POST/PUT methods as content directly.
If I require to use a different request message, I’ll instead add that content to my Request Message, and set the ‘Method’ of the request message to be the one the API requires, as well as loading the request URL :
RequestMessage.Content := RequestContent;
RequestMessage.SetRequestUri(RequestURL);
RequestMessage.Method := 'PATCH';
Now that’s all been put together, I can simply send the request
client.Send(RequestMessage, ResponseMessage);
To avoid repeating the code throughought my integration, and to give me a one stop method for my HTTPclient interactions, I’ve formed this up into a wrapper function:
procedure CallService(ProjectName: Text; RequestUrl: Text; RequestType: Enum HTTPRequestTypeEnum_ADO; payload: Text; Username: Text; Password: Text): Text
var
Client: HttpClient;
RequestHeaders: HttpHeaders;
RequestContent: HttpContent;
ResponseMessage: HttpResponseMessage;
RequestMessage: HttpRequestMessage;
ResponseText: Text;
contentHeaders: HttpHeaders;
begin
RequestHeaders := Client.DefaultRequestHeaders();
RequestHeaders.Add('Authorization', CreateBasicAuthHeader(Username, Password));
case RequestType of
RequestType::Get:
Client.Get(RequestURL, ResponseMessage);
RequestType::patch:
begin
RequestContent.WriteFrom(payload);
RequestContent.GetHeaders(contentHeaders);
contentHeaders.Clear();
contentHeaders.Add('Content-Type', 'application/json-patch+json');
RequestMessage.Content := RequestContent;
RequestMessage.SetRequestUri(RequestURL);
RequestMessage.Method := 'PATCH';
client.Send(RequestMessage, ResponseMessage);
end;
RequestType::post:
begin
RequestContent.WriteFrom(payload);
RequestContent.GetHeaders(contentHeaders);
contentHeaders.Clear();
contentHeaders.Add('Content-Type', 'application/json');
Client.Post(RequestURL, RequestContent, ResponseMessage);
end;
RequestType::delete:
Client.Delete(RequestURL, ResponseMessage);
end;
ResponseMessage.Content().ReadAs(ResponseText);
exit(ResponseText);
end;
The ‘Request Type’ is an enum with the following values:
value(0; Get) { }
value(1; patch) { }
value(2; post) { }
value(3; delete) { }
Pingback: API call using httpclient-GET | My Business Central Diary
nice one jack, helped me out
LikeLike
can explain please the Payload? I have problem with payload. My example is like so
PayLoad := ‘application/json,{includes : { \”product\”: [\”id\”, \”name\”]}}’;
but server returning erorr: “{“errors”:[{“code”:”0″,”status”:”400″,”title”:”Bad Request”,”detail”:”The JSON payload is malformed.”
RequestContent.WriteFrom(PayLoad);
RequestContent.GetHeaders(contentHeaders);
contentHeaders.Remove(‘Content-Type’);
contentHeaders.Add(‘Content-Type’, ‘application/json’);
RequestMessage.Content := RequestContent;
RequestMessage.SetRequestUri(RequestURL);
RequestMessage.Method := ‘POST’;
Client.DefaultRequestHeaders.Add(‘User-Agent’, ‘Dynamics 365’);
client.Send(RequestMessage, ResponseMessage);
LikeLike
Yes – I have a half written post getting into this – but I’d recommend using the BC json object data type and building the payload that way.
LikeLike