How to Create ExchangeRate Provide through Axapta in Ax 2012



The steps required to set up an exchange rate provider. For illustrative purposes, the OANDA exchange rate service is used throughout this white paper. When you follow the steps described in this paper, you will create a functional exchange rate provider. However, that provider is not intended for use in production code. To request a free OANDA test account and for information about the OANDA exchange rate service. Exchange rate providers can be written in .NET or X++, based on the preference and skills of each developer.

1.   Import currency exchange rates – The process that retrieves exchange rates from exchange rate providers and imports them into the application. This is a system operation that supports batch processing.
2.  Exchange rate provider – An X++ or .NET class that is responsible for retrieving exchange rates from external sources.
3.   Exchange rate provider registration – The process of enabling an exchange rate provider for use within the application. By default, exchange rate providers are not registered when they are deployed.
4.  Exchange rate provider configuration – The configuration settings of an exchange rate provider that determine how it will be used in the application.
5.   Exchange rate service – A free or paid subscription service that provides a list of published exchange rates.
6.  The framework – The import currency exchange rates framework that coordinates the retrieval of exchange rates from providers and the proper storage of the exchange rates within Microsoft Dynamics AX.

Follow these steps to create an X++ exchange rate provider.

Create a new class that is derived from the ExchangeRateProvider class.

class ExchangeRateProviderOanda extends ExchangeRateProvider
{
}

Add the ExchangeRateProviderAttribute attribute to enable the exchange rate provider framework to find the new provider during the registration process.

[ExchangeRateProviderAttribute]
class ExchangeRateProviderOanda extends ExchangeRateProvider
{
}

Add the ExchangeRateProviderIdAttribute attribute to uniquely identify the provider. Use a string representation of a GUID to ensure uniqueness. New GUIDs can be created on sites such as http://createguid.com. Also add the following class-level variables, which are specific to this example and are not necessarily required for every provider.

[ExchangeRateProviderAttribute,ExchangeRateProviderIdAttribute('61FDEDDB-B181-4C78-B35E-CBA193A70C75')]
public class ExchangeRateProviderOanda extends ExchangeRateProvider
{
    str serviceUrl;
    str serviceUrlForDateRange;
    str serviceClient;
    int serviceTimeout;
    List rates;
    List dates;
    #define.ExchangeRatePosition(8)
    #define.DatePosition(12)
    #define.RatePrice("bid")
    #define.RateDate("time")
    #define.DateFormatMDY(213)
    #define.ProviderId("FF7BF89C-18C1-42AF-91BA-07BBC3419D39")
    #define.QuoteXPath("//response/quotes/quote")
    #define.BidXPath("//bid")
    #define.DateXPath("//quote/date")
    #define.ServiceTimeout("serviceTimeout")
    #define.ServiceURL("ServiceUrl")
    #define.OANDADateFormat("yyyy-MM-dd")
    #define.HttpWebRequestMethod("GET")
    #define.HttpWebRequestContentType("application/xml")
    #define.HttpHeaderAuthorization("Authorization")
    #define.KeyTokenPrefix("Bearer ")
}

Implement the getName method. The name used in the following code is hard-coded; however, in production code, a label should be used to enable proper translation. The name provided here can be changed by the user when that user sets up the provider’s configuration information.

public ExchangeRateProviderName getName()
{
    return "Oanda exchange rate service New ";
}

Implement the getProviderId method. Return the same GUID string that was used for the ExchangeRateProviderIdAttribute attribute.

public ExchangeRateProviderId getProviderId()
{
    return '61FDEDDB-B181-4C78-B35E-CBA193A70C75';
}

Implement the getSupportedOptions method.
1. Set the parmDoesSupportSpecificCurrencyPairs option to true only if the exchange rate service requires that a source and destination currency be passed to get an exchange rate. Many exchange rate services return rates with respect to a fixed currency or a given set of currency pairs. For these services, the value of this option should be set to false.
2. Set the parmDoesSupportSpecificDates option to true if the exchange rate service enables rates to be requested for specific dates. Services that provide the exchange rate only for the current date will set this value to false.
3. Set the parmFixedBaseIsoCurrency option to the three-character ISO currency code that represents the fixed base currency of the exchange rates being returned from the exchange rate service. If the exchange rate service does not support a fixed base currency, return an empty string. For example, the euro is often used as a fixed base currency. When you create a new provider, be sure to research the exchange rate service to select the correct value.

public ExchangeRateProviderSupportedOptions getSupportedOptions()
{
    ExchangeRateProviderSupportedOptions supportedOptions = ExchangeRateProviderSupportedOptions::construct();
    supportedOptions.parmDoesSupportSpecificCurrencyPairs(false);
    supportedOptions.parmDoesSupportSpecificDates(false);
    supportedOptions.parmFixedBaseIsoCurrency('');
    return supportedOptions;
}

Implement the getConfigurationDefaults method. Configuration defaults are name-value pairs that represent the default configuration settings for the exchange rate provider. These settings will be automatically loaded for use when the provider is registered, but they can be changed by the user. Take the necessary precautions when converting these strings into usable values.

public ExchangeRateProviderConfigDefaults getConfigurationDefaults()
{
    ExchangeRateProviderConfigDefaults configurationDefaults = ExchangeRateProviderConfigDefaults::construct();
    configurationDefaults.addNameValueConfigurationPair('ServiceTimeout', '5000');
    configurationDefaults.addNameValueConfigurationPair('ServiceURL', 'https://www.oanda.com/rates/api/v1/rates/%1.xml? decimal_places=5&date=%2&fields=averages&quote=%3');
configurationDefaults.addNameValueConfigurationPair('serviceClient', 'oandatest');
    return configurationDefaults;
}

Implement the getExchangeRates method. This method uses the configuration information and the provided ExchangeRateRequest class instance to call out to the exchange rate source and return the appropriate ExchangeRateResponse class instance. When writing this method, consider these important points:
1.  Any of the configuration information that is required should be retrieved from the ExchangeRateProviderConfig class. Calling the getPropertyValue method on that class will provide the string representation of the property value for the property key provided. Take the necessary precautions when converting this string value to another type.
2.    Some providers require explicit currency pairs when requesting exchange rates. These are the same providers that would set the ExchangeRateProviderSupportedOptions.parmDoesSupportSpecificCurrencyPairs property to true. When this is the case, it is necessary to use the currency pairs provided by the ExchangeRateRequest class instance to drive the retrieval process. The OANDA provider implementation that follows shows a good example of this.
3.     Other providers that do not support specific currency pairs normally return data for a fixed set of currency pairs. When this is the case, the currency pairs that are provided by the ExchangeRateRequest class instance can be ignored. Providers should return all the rates that are available, and the framework will import the correct ones, based on the user’s decision to automatically create the necessary currency pairs or not. The LithuanianBankProvider provider and CentralBankOfEuropeProvider provider that ship with Microsoft Dynamics AX are good examples of this.
4.   The ExchangeRateRequest class instance has a property called parmImportDateType that indicates what dates should be used to retrieve exchange rates from the service. The two options are CurrentDate and DateRange.
5.   CurrentDate is meant to retrieve the most up-to-date exchange rate from the exchange rate service. When this is passed to the provider, the framework will also set ExchangeRateRequest.parmFromDate and parmToDate to the system date of the Application Object Server (AOS) computer that is making the request. If exchange rate services support the retrieval of exchange rates for specific dates, the date provided by the framework should be passed. If the exchange rate service instead provides a call to get the most current exchange rate (whatever the date may be), it is necessary to check the date returned to ensure that it is less than or equal to the requested date.
6.   DateRange is meant to retrieve a specific date range of exchange rates. In this case, only exchange rates within that date range should be allowed. If an exchange rate service requires that specific dates be included in the request, this is straightforward. If an exchange rate service instead returns a group of historical dates that may be outside the valid range of dates, it is up to the provider to filter out the dates that are not pertinent before passing them back to the framework.
7.     When returning exchange rates, always use the date provided by the exchange rate service rather than the dates supplied by the ExchangeRateRequest class instance. This ensures that the exchange rate returned is associated with the correct date, because there are scenarios where an exchange rate service can return rates for dates that were not expected. For example, if an exchange rate is requested for a date in the future, some providers return the most recent exchange rate instead of throwing an error or returning nothing.
8.    If errors are encountered when you attempt to retrieve exchange rates from the exchange rate service, do not throw custom error messages. The framework will alert the user that something is wrong by throwing generic error messages stating that the expected currency pairs could not be retrieved from the provider. If it is necessary to log additional errors, use the event log instead.

public ExchangeRateResponse getExchangeRates(ExchangeRateRequest _exchangeRateRequest)
{
    ExchangeRateResponse response = ExchangeRateResponse::construct();
    ExchangeRateResponseCurrencyPair currencyPairResponse;
    ExchangeRateResponseExchangeRate exchangeRateResponse;
    ExchangeRateRequestCurrencyPair currencyPairRequest;
    ExchangeRateProviderConfig config = ExchangeRateProviderConfig::construct();
    str oandaRequestString;
    date currentDate;
    CurrencyExchangeRate exchangeRate;
    System.Net.WebResponse webResponse;
    System.IO.StreamReader streamReader;
    System.IO.Stream stream;
    System.Net.HttpWebRequest httpWebRequest;
    ListEnumerator rateEnumerator, dateEnumerator;
    System.Net.WebHeaderCollection webCollection;
    System.DateTime fromDate, fromUTCDate;
    System.TimeZone localTimeZone;
    int compareResult;
    str XMLOut;
    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.
        // If we attempt to make a single request for multiple dates
        // then OANDA will average the rates across the dates which is not what we want.
        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 to UTC which is required by OANDA
            fromUTCDate = localTimeZone.ToUniversalTime(fromDate);
            dateForRequest = fromUTCDate.ToString(#OANDADateFormat);
            // Build the request URL.
            oandaRequestString = strFmt(serviceUrl,
            currencyPairRequest.parmFromCurrency(),
            currencyPairRequest.parmToCurrency(),
            dateForRequest,
            dateForRequest);
            // Configure the request for OANDA.
            httpWebRequest = System.Net.WebRequest::CreateHttp(oandaRequestString);
            httpWebRequest.set_Method(#HttpWebRequestMethod);
            httpWebRequest.set_ContentType(#HttpWebRequestContentType);
            httpWebRequest.set_Timeout(serviceTimeout);
            // Authentication
            webCollection = httpWebRequest.get_Headers();
            webCollection.Add(#HttpHeaderAuthorization, #KeyTokenPrefix + //TODO: Retrieve and concatenate your Key provided by OANDA);
                try
                {
                // Invoke the service
                webResponse = httpWebRequest.GetResponse();
                // Retrieve the XML response.
                stream = webResponse.GetResponseStream();
                streamReader = new System.IO.StreamReader(stream);
                XMLOut = streamReader.ReadToEnd();
                // Parse the XML to retrieve the rate and date.
                this.readRate(XMLOut);
                rateEnumerator = rates.getEnumerator();
                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;
}

Implement the following helper methods. These are specific to this example and are not required for every provider.

public void readRate(str _xmlString)
{
    System.Xml.XmlDocument xmlDom = new System.Xml.XmlDocument();
    System.Xml.XmlNode xmlQuoteNode, xmlBidNode, xmlDateNode;
    CurrencyExchangeRate exchangeRate;
    ValidFromDate exchangeDate;
    str value;
    xmlDom.LoadXml(_xmlString);
    // Find the Quote
    xmlQuoteNode = xmlDom.SelectSingleNode(#QuoteXPath);
    if (xmlQuoteNode)
    {
    // Find the exchange rate
    xmlBidNode = xmlQuoteNode.SelectSingleNode(#BidXPath);
        if (xmlBidNode)
        {
        value = xmlBidNode.get_InnerText();
        exchangeRate = str2num(value);
            if (exchangeRate)
            {
                rates.addEnd(exchangeRate);
            }
        }
    //Find the date of the exchange rate.
    xmlDateNode = xmlQuoteNode.SelectSingleNode(#DateXPath);
        if (xmlDateNode)
        {
        value = xmlDateNode.get_InnerText();
        // convert the date from UTC to local timezone.
        exchangeDate = System.DateTime::Parse(value, System.Globalization.CultureInfo::get_CurrentUICulture(), System.Globalization.DateTimeStyles::AssumeUniversal);
            if (exchangeRate)
            {
                dates.addEnd(exchangeDate);
            }
        }
    }
}

Compile the ExchangeRateProviderOanda class, and then generate incremental CIL. The provider will be run as part of a SysOperation.

Related Posts

Previous
Next Post »