In a lot of other programming languages, the concepts of asynchronous calls and background tasks are well established and supported1. The idea is to have the ability to kick of something in the background without having to wait for the result and thereby blocking the user. Instead the user can just keep working on something else. It also helps if you want to quickly show a form without delay even if some calculation has to be made for some parts of it. Imagine immediately getting a start page or form and then something in the background gets data to display additionally like a share price or weather forecast which might take a bit to fetch. While Dynamics NAV / BC has support for something like that to a degree, the 2019 Release Wave 2 of Business Central expands that support a lot, allowing you to tackle many of those scenarios quite easily.
With a new concept called “Page Background Tasks” you can create child sessions in the background which execute functionality in codeunits. Once the background task is finished, the parent session can get the results and work with them like show it in a field, update a diagram or show a notification. The implementation is done by calling EnqueueBackgroundTask
to kick off a Codeunit with the background task and then handle the results in a trigger called OnPageBackgroundTaskCompleted
or OnPageBackgroundTaskError
if an error happens. For the user, the main benefit is a very quickly starting page where the information generated in the background appears as soon as it is available. As an example, see the following implementation where the user opens the customer card and then loads share price information. The page loads immediately with the already available data – in that case the share name – and the price information appears as soon as the request to an external WebService returns.
As mentioned above, the implementation consists of four parts:
EnqueueBackgroundTask
happen in the OnAfterGetCurrRecord
trigger to start the background tasks2. As parameters EnqueueBackgroundTask
takes a task ID to later correlate the result to the call, the ID of the Codeunit to call, parameters for the background task as Dictionary, a timeout in milliseconds and the criticality level if an error appears. The task ID needs to be a global integer which will be set automatically when the task is enqueued.
TaskParameters.Add('ShareID', ShareID); TaskParameters.Add('Element', PriceKey); CurrPage.EnqueueBackgroundTask(TaskPriceId, 50100, TaskParameters, 20000, PageBackgroundTaskErrorLevel::Warning);
codeunit 50100 PBTGetSharePrice { TableNo = Customer; trigger OnRun() var Result: Dictionary of [Text, Text]; WebServiceKey: Text; BaseURI: Text; HttpClient: HttpClient; HttpResponseMessage: HttpResponseMessage; ContentString: Text; ContentToken: JsonToken; QuoteToken: JsonToken; QuoteArrayToken: JsonToken; ValueToken: JsonToken; ShareId: Text[4]; ElementValue: Text; ElementKey: Text; begin ShareId := Page.GetBackgroundParameters().Get('ShareID'); ElementKey := Page.GetBackgroundParameters().Get('Element'); if (ShareId <> '') then begin WebServiceKey := '<add api token here>'; BaseURI := 'https://api.worldtradingdata.com/api/v1/stock?api_token=' + WebServiceKey; HttpClient.Get(BaseURI + '&symbol=' + ShareId, HttpResponseMessage); if (not HttpResponseMessage.IsSuccessStatusCode) then Error('Couldn''t retrieve the share data'); HttpResponseMessage.Content().ReadAs(ContentString); ContentToken.ReadFrom(ContentString); ContentToken.AsObject().Get('data', QuoteArrayToken); QuoteArrayToken.AsArray().Get(0, QuoteToken); QuoteToken.AsObject().Get(Page.GetBackgroundParameters().Get('Element'), ValueToken); ElementValue := ValueToken.AsValue().AsText(); Sleep((Random(5)) * 1000); end; Result.Add(ElementKey, ElementValue); Page.SetBackgroundTaskResult(Result); end; }
As you can see, first the parameters are read from the parameter Dictionary (lines 20 and 21) using Page.GetBackgroundParameters()
and the WebService is called. Then the result is parsed3 and the requested value is added to the result Dictionary (lines 39 and 40) using Page.SetBackgroundTaskResult()
. For the demo case, I also added a random sleep between one and five seconds (line 36), so that the results come back with a humanly visible delay because the WebService is so fast that without the sleep it seems instantaneous. Of course in a real implementation you wouldn’t include that sleep.
OnPageBackgroundTaskCompleted
to check which task has finished using the task ID variable and then just set the right variable which I show on the page:
page 50100 "Customer Share Details" { ... layout { area(Content) { group(Details) { Caption = 'Share Details'; field(ShareId; ShareID) { ApplicationArea = All; Caption = 'Share name'; } field(SharePrice; PriceVar) { ApplicationArea = All; Caption = 'Current price'; } ... } ... trigger OnPageBackgroundTaskCompleted(TaskId: Integer; Results: Dictionary of [Text, Text]) begin if (TaskId = TaskPriceId) then begin PriceVar := Results.Get(PriceKey); ... end; ... }
OnPageBackgroundTaskError
which works a bit differently if a problem occurs when parsing the result JSON. It “catches” the original error, marks it as handled so that the default mechanism doesn’t handle it and instead throws an error with a slightly more user friendly message:
trigger OnPageBackgroundTaskError(TaskId: Integer; ErrorCode: Text; ErrorText: Text; ErrorCallStack: Text; var IsHandled: Boolean) begin if (ErrorCode = 'JsonPropertyNotFound') then begin IsHandled := true; Error('Couldn''t find data for %1', ShareID); end; end;
If you want to give this a try, you can clone my repository for this example on Github. Don’t forget to get your own API key for the WebService and place it in line 194 of the .al file. You will also need to add your own data in the launch.json file in folder .vscode. After installing the extension, open a customer card and add a share ID like MSFT or GOOG. Then execute action “Share Info” below “Customer” and you should see the page background tasks in action!
If you want to learn more about this feature and get a deep dive, this session at Directions EMEA by Damien Girard, Jens Moller-Pedersen and Kennie Pontoppidan will cover all the details.