How to call a web API from Dynamics 365 FO X++

A short example how to autenticate and talk to web based API from x++

The code below is divided into two pieces/methods just to ease how to do things. One that authenticates to an API and the other one takes the authentication token and adds it to the call. The examples below maybe don’t fit your scenario due to all API are different but the procedure is almost the same.

void authentication() 
{
str                rawResponse;
Map                responseData;
RetailWebResponse  response;
;
//URL to the API, Data, Header data, ContentType: 'application/x-www-form-urlencoded'
response = webApi.makePostRequest([URL], @'grant_type=client_credentials&client_id=[clientId]', [Header], [ContentType]);
if(response.parmHttpStatus() != 200)
throw error("Couldnt authenticate");

rawResponse = response.parmData();
responseData = RetailCommonWebAPI::getMapFromJsonString(rawResponse);
token = responseData.lookup("access_token");
}

In this example below, we got our response back as an ARRAY. we remove the ARRAY tags and with that, the call is translated into JSON.

private void call()
{
RetailWebRequest   webRequest;
RetailWebResponse  response;
str                rawResponse, ref;
Map                responseData;
;

webApi = RetailCommonWebAPI::construct();

//URL to get data
webRequest = RetailWebRequest::newUrl([URL]);
//Send token through JSON GET call
webRequest.parmHeader(@'Authorization: Bearer '+token);
webRequest.parmContentType("application/json");
webRequest.parmMethod("GET");

response = webApi.getResponse(webRequest);

if(response.parmHttpStatus() != 200)
throw error("No data is returned");
rawResponse = substr(response.parmData(),2,(strlen(response.parmData())-2));
responseData = RetailCommonWebAPI::getMapFromJsonString(rawResponse);

ref        = responseData.lookup("ref");
}

“Reversed” wild card search

Sometimes you would want to do a “reversed” wild card search, for example if you want to find out if a customer in any way matches a wild card stored in another table.

Here is a scenario:

You have a table and a form that stores customers that should be blocked in any way.
Here you can use the standard AX wild cards to indicate that a group of customers with partly the same name should be blocked.

Then, you want to find out if a specific customer should be blocked according to the table above.

Here is what you would need to do:

First of all you have to make sure that the table has the property CacheLookup set to ‘EntireTable’. Otherwise this wild card search won’t work.

Then, you can write this piece of code to check if the specific customer matches any of the wild cards in the table FO_BlockCustomer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void FO_ReversedWildCardSearch(Args _args)
{
    FO_BlockCustomer tblBlockCustomer;
    CustTable        tblCustTable = CustTable::find('DE-010');
    Name             name;
 
    if (tblCustTable)
    {
        name = tblCustTable.name();
 
        select firstOnly tblBlockCustomer where
        (name like tblBlockCustomer.Name);
 
        if (tblBlockCustomer.RecId)
            info(name + " should be blocked");
        else
            info(name + " should NOT be blocked");
    }
}

Checking customer ‘DE-010’ will return the following info log.

Cannot add or remove static keyword for this method

A while ago while delivering code for a customer (in Microsoft Dynamics AX 2012) I ran in to this problem while compiling with axbuild.exe (but not while compiling in the aot).
I haven’t been able to reproduce this error after fixing the problem, but from what I read there might be several ways to reproduce it.

So, how did I fix this?
First things first.. What does the error message mean and why does it only appear while using axbuild.exe?
Since last compilation of the object the static keyword on the method you are trying to compile has been removed or added.
When the static keyword is being added/removed you are note only changing the plain text code of the method, but also the type of the object.

The type of an aot object is stored in the model database in the table “ModelElement”.
As you can see there is a field named “ElementType”.
2016-05-30 16-52-35

And here is Microsoft’s list of types.

The problem when you get this error message is that the element type is not in sync with the plain text code.
For example: Your method is saved as a static method, but in the “ModelElement” table the element type is still 12 (TableInstanceMethod) where it should be 22 (TableStaticMethod).
For some reason the compiler in the aot does not validate this, but the axbuild.exe compiler does.

Here is a comparison I did between the source and destination environment during my installation of code.
5FFA24F0

Changing this directly in sql with “Sql Server Management Studio” will do the trick.

Sales line number not unique

There are cases in all current versions of Microsoft Dynamics AX where the line number on sales order lines is not incrementally assigned and can therefore be assigned in duplicates.

For the purpose of the post i have added the field “Linenum” to the grid in the sales order form.

As you can see below the line numbers are assigned incrementally when creating order lines in an ordinary fashion. (one by one).
2016-05-27 10-35-15

Some sellers might decide to add a few empty sales lines to the order before picking the item number like this.
2016-05-27 10-44-41

At this point the line number still looks normal.
But lets see what happens when when we start picking values for these lines and saving them.
2016-05-27 10-48-05

In this example i start by assigning an item number for the last row and then for the first row.
As soon as the item number is assigned and i leave the field, the line number is changed to 3.

The first thing you might ask yourself is: “Why the heck would you create order lines this way?”
That i can’t answer, but i know for sure that it’s not totally uncommon.

If sales line number are not unique you might run in to problems with some customizations that uses the line number to identify the sales line.

Our solution for this is to enforce a more “normal” behavior by adding a piece of code that deletes all cached sales lines every time a sales line is saved.

Simply add this method to the data source “SalesLine” in the form “SalesTable”.

private void HandleMultipleCacheLines()
{
    SalesLine   salesLineLineNum;
    ;
 
    salesLineLineNum = salesLine_ds.getFirst();
 
    while (salesLineLineNum.SalesId)
    {
        if (!salesLineLineNum.RecId)
        {
            salesLine_ds.cacheRemoveRecord(salesLineLineNum);
        }
 
        salesLineLineNum = salesLine_ds.getNext();
    }
 
    salesLine_ds.reread();
    salesLine_ds.refresh();
}

And call the new method from within the method “write” on the data source “SalesLine”.
Put the call right below “super();”.

Please let me know if you have a another solution.

DMF – DMF Service is unavailable

I got this message in the Beta 2 version of the Data Migration Framework in Dynamics AX 2012.

This link is an important piece of information that you will need to understand some of the errors you get while trying to set up some of the new functionality in BETA 2, such as ODBC connection as a source data format.

http://technet.microsoft.com/en-us/library/jj225595.aspx

I got this message when I was trying to validate the connection string to an ODBC database. The reason for this was that the original installation had failed because of some unknown reason.

DMF Service is unavailable

It´s important to know that we, in this case, have the installation of the server integration services on the same server as the AOS.

In Beta 1 the installation resulted in one new folder under program files, in Beta 2 there should be three folders.

1. Microsoft Dynamics AX 2012 Data Migration Framework Server Components(Beta)
2. Microsoft Dynamics AX 2012 Data Migration Framework Client Components(Beta)
3. Microsoft Dynamics AX 2012 Data Migration Framework Service (Beta)

In our case the Service folder and it´s content was missing. Knowing this, the error message I got started to make a lot more sense.

This is how you test if your service is up and running. Log in to your AOS server and open a browser, then enter this URL in your web browser:

http://DMF-Service-ComputerName:7000/DMFService/DMFServiceHelper.svc

Of course you have to change the “DMF-Service-ComputerName” part to your server name first.

When we tried this, the service was not up and running. What we did was to turn of the AOS service and then run the installation process again for the service part of the DMF installation. After it was finished our “Microsoft Dynamics AX 2012 Data Migration Framework Service (Beta)” folder was where is should have been all along, under Program files.

We then started the AOS service and tried again. This time we could both access the service using the URL in a browser and validate the connection string to the database on the odbc source data format.

On to the next challenge.

DMF – Error occured while doing bulkcopy from temp table to entity table

This is a generic error message you get in data migration framework from time to time.

Error occured while doing bulkcopy from temp table to entity table

I have found that the reason for this could one of many. This is the ones I have encountered.

Continue reading DMF – Error occured while doing bulkcopy from temp table to entity table

DMF – Cannot create a record in Product number (EcoResProductIdentifier)

When working with the new Data migration framework module in Dynamics AX 2012 you will encounter a lot of problems and strange errors. One I recently encountered is this error message.

“Cannot create a record in Product number (EcoResProductIdentifier). The record already exists”

Continue reading DMF – Cannot create a record in Product number (EcoResProductIdentifier)

Date and UtcDateTime interval for previous month

This code calculates the first and last date of the previous month.

1
2
3
4
5
6
7
8
9
10
11
12
static void EP_dateMonthInterval(Args _args)
{
    date            fromDate;
    date            toDate;
    date            baseDate = systemDateGet();
    ;
 
    toDate      = dateStartMth(baseDate) - 1;
    fromDate    = dateStartMth(toDate);
 
    info(strFmt("%1 - %2", fromDate, toDate));
}

UtcDateTime version of the same job.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void EP_UtcdateMonthInterval(Args _args)
{
    UtcDateTime     fromDate;
    UtcDateTime     toDate;
    UtcDateTime     baseDate = DateTimeUtil::utcNow();
    ;
 
    // Remove this month number of days.
    toDate = DateTimeUtil::addDays(
    baseDate, -(DateTimeUtil::day(baseDate) -1));
 
    // Add the rest of this days time minus one second.
    toDate = DateTimeUtil::addSeconds(
    toDate, -(DateTimeUtil::time(toDate)+1));
 
    // Remove the number of days we are on in
    // the previous month and add one second to 
    // get to the first of the month.
    fromDate = DateTimeUtil::addSeconds(
    DateTimeUtil::addDays(
    toDate, -(DateTimeUtil::day(toDate))), 1);
 
    info(strFmt("%1 - %2", fromDate, toDate));
}

Maps and MapEnumerators

This code put five records of the table CustTable in a map with the key AccountNum. Then the map is looped and the and AccountNum and customer Name is printed to the infolog.

The last part of the code finds the last AccountNum from from the loop directly from the Map, but after a check is done if the value is in the map. Performing a lookup for a value that doesn´t exist in map will result in a error.
Continue reading Maps and MapEnumerators

Advanced filter, day/month/year

It’s been a while since the last post but here is a useful tip to improve your filtering in Dynamics AX 2009. Thanks to Filip Carnbäck.

If you need to set an advanced filter on an interval you can use the following:

dayRange
monthRange
yearRange

Use dayRange for example like this: (dayRange(-2,2)) This will filter out all transactions two days before today and two days from today. Use monthRange and yearRange in the same way. This feature is among other useful when you setup a report to print in batch and the filter on date must change for each printout.

Also try these features when you filter:

(greaterThanDate(-2))

(lessThanDate(0))

Example dayRange