The new version of the Microsoft Dynamics 365 Business Central brought a new system module “Azure Functions” that makes integration with Azure Functions much easier and straightforward. The typical scenario, why to use Azure Functions together with Business Central, is if we need to use the .NET interop component (for example custom DLL) from AL. Some examples of why I use Azure Functions are creating integration with SFTP (to send warehouse data to old-school shipping agents), loading data from machines in manufacturing, or just signing documents using SignPads.
The new module contains three (public) codeunits.
- Azure Functions (codeunit 7804)
- Azure Functions Authentication (codeunit 7800)
- Azure Functions Response (codeunit 7805)
Authentication
Let’s start with authentication. There are two possible ways how to use this module from the point of authentication. If your Azure Function is secured at function-level only (= without user identification), we can use the method CreateCodeAuth(…) from Codeunit 7800 “Azure Functions Authentication”. This method accepts two parameters, Azure Functions endpoint (URL) and authentication code that is defined for your Azure Function. This approach is far from ideal as anyone who knows this code can access your Azure Functions.
The second method is CreateOAuth2(…) which allows creating a connection with both OAuth2 and the Authentication code for your function.
Both these methods return an instance of codeunit that implements an interface “Azure Functions Authentication” which is later used for API requests.
Create own integration
Once we know the authentication methods, we know everything necessary to build our integration. To send a GET request to our Azure Function we can use the method SendGetRequest(…) available in Codeunit 7804 “Azure Functions”.
This method has two parameters, the first one is the implementation of Azure Functions Authentication that we discussed earlier. The second one is a Dictionary of Texts that will be translated to URL parameters (the dictionary key = parameter name, dictionary value = parameter value).
Method SendGetRequest(…) returns an instance of another codeunit, Codeunit 7805 “Azure Functions Response”. This object contains all information about the API response, including whether the API call was successful (method IsSuccessful()). You can also get error information (GetError()), response content (GetResultAsText(…), GetResultAsStream(…)) or whole instance of HttpResponseMessage (GetHttpResponse(…)).
procedure SendAzureFunctionGetRequest()
var
AzureFunctions: Codeunit "Azure Functions";
AzureFunctionsResponse: Codeunit "Azure Functions Response";
AzureFunctionsAuthentication: Codeunit "Azure Functions Authentication";
IAzureFunctionsAuthentication: Interface "Azure Functions Authentication";
QueryDictinary: Dictionary of [Text, Text];
begin
QueryDictinary.Add('param1', 'value1');
QueryDictinary.Add('param2', 'value2');
QueryDictinary.Add('param3', 'value3');
IAzureFunctionsAuthentication := AzureFunctionsAuthentication.CreateCodeAuth('AZURE FUNCTION ENDPOINT', 'AUTHENTICATION CODE');
AzureFunctionsResponse := AzureFunctions.SendGetRequest(IAzureFunctionsAuthentication, QueryDictinary);
if AzureFunctionsResponse.IsSuccessful() then
Message('Get request successful.')
else
Error('Get request failed.\Details: %1', AzureFunctionsResponse.GetError());
end;
The POST request could be created similarly – we just need to use the SendPostRequest(…) method instead of SendGetRequest(…). This time, the method does not accept Dictionary, but RequestBody (only as Text) and Content-Type header.
Unfortunately, I really miss something like SendPostRequest(AzureFunctionAuthentication: Interface “Azure Functions Authentication”; Body: JsonObject) as it will be much easier to use…
procedure SendAzureFunctionPostRequest()
var
AzureFunctions: Codeunit "Azure Functions";
AzureFunctionsResponse: Codeunit "Azure Functions Response";
AzureFunctionsAuthentication: Codeunit "Azure Functions Authentication";
IAzureFunctionsAuthentication: Interface "Azure Functions Authentication";
RequestBody: Text;
JsonObject: JsonObject;
begin
JsonObject.Add('param1', 'value1');
JsonObject.Add('param2', 'value2');
JsonObject.Add('param3', 'value3');
JsonObject.WriteTo(RequestBody);
IAzureFunctionsAuthentication := AzureFunctionsAuthentication.CreateCodeAuth('AZURE FUNCTION ENDPOINT', 'AUTHENTICATION CODE');
AzureFunctionsResponse := AzureFunctions.SendPostRequest(IAzureFunctionsAuthentication, RequestBody, 'application/json');
if AzureFunctionsResponse.IsSuccessful() then
Message('Post request successful.')
else
Error('Post request failed.\Details: %1', AzureFunctionsResponse.GetError());
end;
There is also an available universal method Send(…), which allows using any HTTP Request Type and defining both Header & Body content.
procedure SendAzureFunctionUniversalRequest()
var
AzureFunctions: Codeunit "Azure Functions";
AzureFunctionsResponse: Codeunit "Azure Functions Response";
AzureFunctionsAuthentication: Codeunit "Azure Functions Authentication";
IAzureFunctionsAuthentication: Interface "Azure Functions Authentication";
HttpRequestType: Enum "Http Request Type";
QueryDictinary: Dictionary of [Text, Text];
RequestBody: Text;
JsonObject: JsonObject;
begin
QueryDictinary.Add('urlParam1', 'urlValue1');
QueryDictinary.Add('urlParam2', 'urlValue2');
JsonObject.Add('bodyParam1', 'bodyValue1');
JsonObject.Add('bodyParam2', 'bodyValue2');
JsonObject.WriteTo(RequestBody);
IAzureFunctionsAuthentication := AzureFunctionsAuthentication.CreateCodeAuth('AZURE FUNCTION ENDPOINT', 'AUTHENTICATION CODE');
AzureFunctionsResponse := AzureFunctions.Send(IAzureFunctionsAuthentication, HttpRequestType::/*METHOD*/, QueryDictinary, RequestBody, 'application/json');
if AzureFunctionsResponse.IsSuccessful() then
Message('Universal request successful.')
else
Error('Universal request failed.\Details: %1', AzureFunctionsResponse.GetError());
end;
In my opinion, this new system module made the integration with Azure Functions a bit easier as we do not longer need to set all headers manually and also we do not have to think about the implementation of authentication.
What do you think? Let me know by sharing this article on Twitter/LinkedIn.