Ohh no, my AppSource app stopped working

A while back, I showed how to put an app on AppSource. I used an exchange rate downloader that was coded in an earlier video as the example app. The app was using a free service on the internet that has disappeared. So in this video, I show how I’m converting the app into using a different service, floatrates.com.

https://youtu.be/lRorl2S_sBA

In this video, Erik walks through the process of fixing a broken Business Central app that previously pulled exchange rates from a now-defunct service called openrates.io. He repurposes the app to use a new service called FloatRates, demonstrating how to work with HTTP clients, JSON parsing, date conversion quirks, and the currency exchange rate table in AL — all in about 30 minutes of live coding.

The Problem: A Dead API

Erik had previously built a simple Business Central app as a demonstration for getting an app onto AppSource. The app pulled down exchange rates from a service called openrates.io. Unfortunately, that service no longer exists, which means the app is effectively dead.

Rather than abandon it, Erik decided to repurpose the app to use a different service — and since someone had asked for another example of working with JSON and web services in AL, this was a perfect opportunity to demonstrate that as well.

The New Service: FloatRates

The replacement service Erik chose is FloatRates. The API is straightforward: you request the latest rates for a base currency, and it returns a JSON feed. When formatted nicely, the JSON structure looks something like this:

{
  "usd": {
    "code": "USD",
    "alphaCode": "USD",
    "numericCode": "840",
    "name": "U.S. Dollar",
    "rate": 0.74,
    "date": "Sat, 31 Oct 2020 12:00:01 GMT",
    "inverseRate": 1.35
  },
  "eur": {
    "code": "EUR",
    "alphaCode": "EUR",
    "numericCode": "978",
    "name": "Euro",
    "rate": 0.64,
    "date": "Sat, 31 Oct 2020 12:00:01 GMT",
    "inverseRate": 1.57
  }
  // ... more currencies
}

Each currency is a key in the top-level JSON object, and within each currency object you get the code, name, rate, date, and inverse rate.

Building the HTTP Client Call

Erik started by creating a new function called GetFloatRates that follows the same pattern as the old service call: create an HTTP client, make a GET request, and process the response.

procedure GetFloatRates()
var
    Client: HttpClient;
    Response: HttpResponseMessage;
    ContentTxt: Text;
    Rates: JsonObject;
    Rate: JsonObject;
    T: JsonToken;
    XRate: Decimal;
    ExDate: DateTime;
    Setup: Record "General Ledger Setup";
    CurrExRate: Record "Currency Exchange Rate";
    Currency: Record Currency;
    TypeHelper: Codeunit "Type Helper";
begin
    Setup.Get();
    if Setup."LCY Code" = '' then
        Error('You must specify a LCY Code in %1 to use this function', Setup.TableCaption());

    if Client.Get('https://www.floatrates.com/daily/' + LowerCase(Setup."LCY Code") + '.json', Response) then begin
        if Response.IsSuccessStatusCode() then begin
            if Response.Content.ReadAs(ContentTxt) then begin
                if Rates.ReadFrom(ContentTxt) then begin
                    // Process the rates...
                end;
            end else
                Error('Server did not return any data');
        end else begin
            // Error handling for unsuccessful status codes
        end;
    end else begin
        // Error handling for connection failures
    end;
end;

Understanding the Response Checks

Erik walked through several layers of validation:

  1. Client.Get() — If this fails, it’s usually due to environmental issues like being out of resources or the call being blocked.
  2. Response.IsSuccessStatusCode() — You could also check if Response.HttpStatusCode = 200, but IsSuccessStatusCode covers additional success codes, making it the preferred approach.
  3. Response.Content.ReadAs(ContentTxt) — Reads the response body into a text variable. There are two overloads: one reads into a string, the other into a stream.
  4. Rates.ReadFrom(ContentTxt) — Parses the text string into a JSON object.

The Extension Management Blocked-by-Environment Setting

Erik briefly showed the Extension Management page in Business Central, where you can configure whether an app’s HTTP calls are allowed or blocked by the environment. This is a common reason for HTTP calls failing — the extension simply isn’t allowed to make outbound calls. You can toggle this setting in the app’s configuration.

Parsing the JSON and Looping Through Currencies

Once the JSON is successfully parsed into a JsonObject, Erik loops through the currency records in Business Central and tries to find matching rates in the JSON response:

if Currency.FindSet() then
    repeat
        if Rates.Get(LowerCase(Currency.Code), T) then begin
            Rate := T.AsObject();
            if Rate.Get('inverseRate', T) then
                XRate := T.AsValue().AsDecimal();
            if Rate.Get('date', T) then
                ExDate := TypeHelper.EvaluateUTCDateTime(T.AsValue().AsText());
            // Insert or update exchange rate...
        end;
    until Currency.Next() = 0;

A few key points from this section:

  • The FloatRates JSON uses lowercase currency codes as keys, so LowerCase(Currency.Code) is necessary for the lookup.
  • The JsonToken type can represent an object, array, or value — the three basic JSON structures. After getting a token, you cast it to the appropriate type using AsObject(), AsValue(), etc.
  • For the exchange rate, Erik reads the inverseRate field as a decimal using T.AsValue().AsDecimal().

The Date Parsing Challenge

This is where things got interesting. The date format returned by FloatRates looks like this:

"Sat, 31 Oct 2020 12:00:01 GMT"

This is the RFC 1123 pattern, commonly used in certain internet protocols — but it’s not the standard JSON date format. Attempting to use T.AsValue().AsDateTime() fails with an error: “Unable to convert JSON value to NAV DateTime.”

The Solution: Type Helper Codeunit

Erik used the Type Helper codeunit, which has a function called EvaluateUTCDateTime. This function takes a text string and returns a DateTime:

ExDate := TypeHelper.EvaluateUTCDateTime(T.AsValue().AsText());

Under the hood, this function uses .NET’s TryParseExact with the “R” standard format specifier (which corresponds to the RFC 1123 pattern). Erik pointed out that there’s a separate video about the Type Helper codeunit because it contains many useful functions for type conversion scenarios like this one.

A Subtle Bug: Primary Key Type Mismatch

Erik ran into a bug that he called “a classic” and “a great example of something that looks totally fine.” When trying to do a Get on the Currency Exchange Rate table, the code broke with an unhelpful error message: “The date is not valid.”

The problem? The primary key of the Currency Exchange Rate table consists of the currency code and a Starting Date field (which is a Date type). But ExDate was a DateTime. The AL compiler doesn’t catch this mismatch — it allows the code to compile and then attempts an internal type conversion at runtime, which fails.

The fix is simple — use the DT2Date function to convert the DateTime to a Date:

if not CurrExRate.Get(Currency.Code, DT2Date(ExDate)) then begin
    CurrExRate.Init();
    CurrExRate."Currency Code" := Currency.Code;
    CurrExRate."Starting Date" := DT2Date(ExDate);
    CurrExRate.Insert();
end;
CurrExRate.Validate("Exchange Rate Amount", 1);
CurrExRate.Validate("Relational Exch. Rate Amount", XRate);
CurrExRate.Modify(true);

Erik noted that the Get function could use some improvements from Microsoft — ideally, the compiler should catch type mismatches on primary key fields rather than letting them silently fail at runtime.

Success: Exchange Rates Updated

After fixing the date conversion issue, Erik removed his breakpoints and ran the app. All currency exchange rates were successfully updated with data from October 31st (Halloween, fittingly). The rates looked correct — for example, it cost about 1.2 Canadian dollars to buy a US dollar, and roughly 19 cents to buy a Danish krone.

Running the function a second time also worked correctly, confirming that the “get or insert” logic handled both new and existing records properly.

Key Takeaways

  • HTTP Client pattern in AL: Use HttpClient.Get(), check for success, read the content, then parse the JSON — each step with proper error handling.
  • JSON parsing: Use JsonObject, JsonToken, and JsonValue to navigate JSON structures. Tokens can be cast to objects, arrays, or values as needed.
  • Non-standard date formats: When JSON returns dates in formats like RFC 1123, the Type Helper codeunit’s EvaluateUTCDateTime function is your friend.
  • Watch out for type mismatches: The AL compiler won’t always catch when you pass a DateTime where a Date is expected (such as in Record.Get() calls). Use DT2Date() to convert explicitly.
  • Extension management: Remember that outbound HTTP calls can be blocked at the environment level — check the extension’s configuration if calls are failing.

Conclusion

In about 30 minutes of live coding (with Halloween fireworks in the background), Erik successfully repurposed a broken Business Central app to use a new exchange rate service. The video is a great walkthrough of real-world AL development — dealing with HTTP calls, JSON parsing, unexpected date formats, and subtle runtime bugs that the compiler doesn’t catch. The app is now ready to be cleaned up and resubmitted through AppSource validation.