Microsoft Dynamics AX 2012 has the frame work for integrating currency exchange rates from exchange rate providers. Please reference Creating Exchange Rate Providers for Microsoft Dynamics AX 2012. The referenced document from Microsoft shows how to customize the frame work to work with Oanda Exchange Rates .
There are other providers, some which offer a fee based API and others which offer a free API. Doing research on these providers led me to Open Exchange Rates. They offer an API at no cost for personal use, and a fee based API for Enterprise users, which is fairly competitive with other providers.
This blog entry uses the referenced document from Microsoft as a guide to create a provider class in Dynamics AX specifically for use with the free version of Open Exchange Rates, and the fee based Open Exchange Rates API.
A challenge with using this provider is that it uses JSON instead of XML.
The provider class we will create will be the fee based provider. The fee based provider allows for selection of base currency, and specific currency pairs.
Create the class as ExchangeRateProviderOpenExchRates and have it extend the ExchangeRateProvider as shown in the class declaration below.
Note: The ExchangeRateProviderIdAttribute is a unique ID that you will need to supply. You can obtain a unique ID from http://createguid.com
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ExchangeRateProviderAttribute, | |
ExchangeRateProviderIdAttribute('F3D5E522-7EFD-4CBF-895E-6DF6596DF8DF')] | |
class ExchangeRateProviderOpenExchRates extends ExchangeRateProvider | |
{ | |
str serviceUrl; | |
str serviceUrlForDateRange; | |
str serviceClient; | |
int serviceTimeout; | |
List rates; | |
List dates; | |
//AppId for account identification when sending requests to Open Exchange Rates | |
//You will need to get your own Appid from https://openexchangerates.org | |
#define.AppId("get your own appId") | |
#define.ServiceTimeout("serviceTimeout") | |
#define.ServiceURL("ServiceUrl") | |
#define.OPENEXCHRATESDateFormat("yyyy-MM-dd") | |
} |
Now create a method called getConfigurationDefaults. This method will provide the default URL used to make requests to the provider.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ExchangeRateProviderConfigDefaults getConfigurationDefaults() | |
{ | |
ExchangeRateProviderConfigDefaults configurationDefaults = ExchangeRateProviderConfigDefaults::construct(); | |
configurationDefaults.addNameValueConfigurationPair(#ServiceTimeout, '5000'); | |
//Requires Enterprise account | |
configurationDefaults.addNameValueConfigurationPair(#ServiceURL, 'http://openexchangerates.org/api/historical/%1.json?app_id=%2&base=%3&symbols=%4'); | |
return configurationDefaults; | |
} |
Next create the getExchangeRates method. This method is responsible for constructing the URL to request the currency information from the provider, and store the results in the currency exchange rate tables of Dynamics AX.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ExchangeRateResponse getExchangeRates(ExchangeRateRequest _exchangeRateRequest) | |
{ | |
ExchangeRateResponse response = ExchangeRateResponse::construct(); | |
ExchangeRateResponseCurrencyPair currencyPairResponse; | |
ExchangeRateResponseExchangeRate exchangeRateResponse; | |
ExchangeRateRequestCurrencyPair currencyPairRequest; | |
ExchangeRateProviderConfig config = ExchangeRateProviderConfig::construct(); | |
RetailCommonWebAPI webApi; | |
RetailWebRequest webRequest; | |
RetailWebResponse webResponse; | |
str openExchRatesRequestString; | |
date currentDate; | |
CurrencyExchangeRate exchangeRate; | |
ListEnumerator rateEnumerator, dateEnumerator; | |
System.DateTime fromDate, fromUTCDate; | |
System.TimeZone localTimeZone; | |
int compareResult; | |
str JSONOut; | |
str dateForRequest; | |
rates = new List(Types::Real); | |
dates = new List(Types::Date); | |
localTimeZone = System.TimeZone::get_CurrentTimeZone(); | |
// Iterate over the requested currency pairs. This is only required for providers | |
// that support specific currency pairs. | |
_exchangeRateRequest.initializeCurrencyPairEnumerator(); | |
while(_exchangeRateRequest.moveNextCurrencyPair()) | |
{ | |
serviceTimeout = str2int(config.getPropertyValue(this.getProviderId(), #ServiceTimeout)); | |
serviceUrl = config.getPropertyValue(this.getProviderId(), #ServiceURL); | |
// Process each date separately. | |
fromDate = _exchangeRateRequest.parmFromDate(); | |
compareResult = fromDate.CompareTo(_exchangeRateRequest.parmToDate()); | |
while (compareResult <= 0) | |
{ | |
currencyPairRequest = _exchangeRateRequest.getCurrentCurrencyPair(); | |
currencyPairResponse = ExchangeRateResponseCurrencyPair::construct(); | |
currencyPairResponse.parmFromCurrency(currencyPairRequest.parmFromCurrency()); | |
currencyPairResponse.parmToCurrency(currencyPairRequest.parmToCurrency()); | |
// All rates are requested with a display factor of 1 for this provider. If the | |
// rates internally are represented using a different exchange rate display | |
// factor, the framework will make the necessary adjustments when saving the | |
// exchange rates. | |
currencyPairResponse.parmExchangeRateDisplayFactor(ExchangeRateDisplayFactor::One); | |
// convert to UTC which is required by OPENEXCHANGERATES | |
fromUTCDate = localTimeZone.ToUniversalTime(fromDate); | |
dateForRequest = fromUTCDate.ToString(#OPENEXCHRATESDateFormat); | |
// Build the request URL. | |
openExchRatesRequestString = strFmt(serviceUrl, | |
dateForRequest, | |
#AppId, | |
currencyPairRequest.parmFromCurrency(), | |
currencyPairRequest.parmToCurrency()); | |
// Configure the request for OPENEXCHRATES. | |
webApi = RetailCommonWebAPI::construct(); | |
webRequest = RetailWebRequest::newUrl(openExchRatesRequestString); | |
try | |
{ | |
// Invoke the service | |
webResponse = webApi.getResponse(webRequest); | |
JSONOut = webResponse.parmData(); | |
// Parse the JSON to retrieve the rate and date. | |
this.readRate(JSONOut,currencyPairRequest,DateTimeUtil::date(Global::CLRSystemDateTime2UtcDateTime(fromDate))); | |
rateEnumerator = rates.getEnumerator(); | |
rateEnumerator.moveNext(); | |
dateEnumerator = dates.getEnumerator(); | |
// Create the Exchange Rate Provider Response. | |
dateEnumerator.moveNext(); | |
exchangeRate = rateEnumerator.current(); | |
currentDate = dateEnumerator.current(); | |
if (currentDate != dateNull() && exchangeRate) | |
{ | |
exchangeRateResponse = ExchangeRateResponseExchangeRate::construct(); | |
exchangeRateResponse.parmValidFrom(currentDate); | |
exchangeRateResponse.parmExchangeRate(exchangeRate); | |
currencyPairResponse.addExchangeRate(exchangeRateResponse); | |
currentDate = dateNull(); | |
exchangeRate = 0; | |
} | |
} | |
catch (Exception::CLRError) | |
{ | |
// The service call did not complete. Swallow the exception and try the next | |
// currency pair. The framework will be able to determine which currency | |
// pairs were successfully retrieved and will display the appropriate | |
// warnings to the user. | |
} | |
response.addOrUpdateCurrencyPair(currencyPairResponse); | |
rates = new List(Types::Real); | |
dates = new List(Types::Date); | |
fromDate = fromDate.AddDays(1); | |
compareResult = fromDate.CompareTo(_exchangeRateRequest.parmToDate()); | |
} | |
} | |
return response; | |
} |
Next create the getName method. This method is used by Dynamics AX to populate the list of providers that Dynamics AX has been modified to support.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ExchangeRateProviderName getName() | |
{ | |
return "Open Exchange Rates"; | |
} |
Once the getName method is created, create the getProviderId method. This method simply returns the unique GUID that was assigned in the class declaration.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ExchangeRateProviderId getProviderId() | |
{ | |
return 'F3D5E522-7EFD-4CBF-895E-6DF6596DF8DF'; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ExchangeRateProviderSupportedOptions getSupportedOptions() | |
{ | |
ExchangeRateProviderSupportedOptions supportedOptions = ExchangeRateProviderSupportedOptions::construct(); | |
supportedOptions.parmDoesSupportSpecificCurrencyPairs(true); | |
supportedOptions.parmDoesSupportSpecificDates(false); | |
supportedOptions.parmFixedBaseIsoCurrency(''); | |
return supportedOptions; | |
} |
The last method that needs created is the readRate method. It is responsible for parsing the JSON response string from the provider, and storing the exchange rate and currency information in the Lists.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void readRate(str _jsonString,ExchangeRateRequestCurrencyPair _currencyPairRequest,date _currentDate) | |
{ | |
Map ratesMap = new Map(Types::String,Types::Real); | |
MapEnumerator me; | |
//I have not found a good way to deserialize JSON, so I have written custom code to parse the JSON string | |
void getRates() | |
{ | |
int pos = strScan(_jsonString,'"rates": {' ,0,strlen(_jsonString))+10; // determine position of currency rates in JSON string | |
str temp = subStr(_jsonString,pos,strLen(_jsonString)-pos); // get currency rates into temp string | |
List listRates = strSplit(temp,","); //split the currency rate string into a list | |
List listCountry; //list used to temporarily store country specific currency and rates | |
ListEnumerator lir,lic; //enumerators for rates and country specific rates | |
str strRate; | |
str strCountry; | |
str strCountryRate; | |
lir = listRates.getEnumerator(); | |
//look through all rates | |
while (lir.moveNext()) | |
{ | |
//strRate includes both the ToCurrency Code and the Rate | |
strRate = lir.current(); | |
//split the country specific rate into it's own list so we can add it to the ratesMap | |
listCountry = strSplit(strRate,":"); | |
lic = listCountry.getEnumerator(); | |
//get the 'TO' country specific currency | |
if (lic.moveNext()) | |
{ | |
strCountry = lic.current(); | |
//get the 'TO' country specific rate | |
if (lic.moveNext()) | |
{ | |
strCountryRate = lic.current(); | |
strCountry = strReplace(strCountry,'"',''); | |
//build map of 'TO' country specific currencies and rates | |
ratesMap.insert(subStr(strCountry,strLen(strCountry)-2,3),any2real(strCountryRate)); | |
} | |
} | |
} | |
} | |
//Parse JSON string into ratesMAP | |
getRates(); | |
if (ratesMap.exists(_currencyPairRequest.parmToCurrency())) | |
{ | |
rates.addEnd(ratesMap.lookup(_currencyPairRequest.parmToCurrency())); | |
dates.addEnd(_currentDate); | |
} | |
} |
The version of the class that supports the free API is basically the same except for the URL request string. The free version only supports 2 options. The APPId, and the date parameter. The result is a currency list for all supported currencies but it is USD based. However the class filters to just the currency conversion needed by date. To modify the class to use the free version, simply change the getConfigurationDefaults method as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public ExchangeRateProviderConfigDefaults getConfigurationDefaults() | |
{ | |
ExchangeRateProviderConfigDefaults configurationDefaults = ExchangeRateProviderConfigDefaults::construct(); | |
configurationDefaults.addNameValueConfigurationPair(#ServiceTimeout, '5000'); | |
//using free account | |
configurationDefaults.addNameValueConfigurationPair(#ServiceURL, 'http://openexchangerates.org/api/historical/%1.json?app_id=%2'); | |
return configurationDefaults; | |
} |
Now that the class has been created, it can be used in currency configuration and setup within Dynamics AX.

4 comments:
Hi Joe,
I am also trying to create new provider for exchange rate but not getting the new provider in lookup. I am working on client, I ran incremental CIL, changed GUID. and Restart my AOS. anything else that I am missing plz advise.
Hi Pramod, I had the same issue creating a new provider.
Solution: go Tools -> Cache -> Refresh Elements
I've discovered this after compiling, generating CIL a lot of times with no success, so I trying to clear the AX cache.
I hope that solve your problem.
Leo
After extending the class, make sure to add it to the list of configured exchange rate providers. Since it is a new class you may have to refresh the elements or restart the AOS. Then you must run the Import currency exchange rates.
Stainless Steel vs Titanium Apple Watch | iTaniumArts
The Apple Watch is an open-source version titanium mens wedding bands of the Watch which titanium dental allows you to set citizen titanium dive watch the minimum titanium pot bet, then add the babylisspro nano titanium hair dryer required amount to your bet slip.
Post a Comment