Follow these steps to
create a .NET exchange rate provider.
Install the
Microsoft Visual Studio Tools.
Configure
the AOS servers for hot swapping. This enables the newly created exchange rate
provider to be deployed without requiring a restart of AOS.
Create a new
Visual Studio .NET class assembly project. For this example, a C# project was
used.
Add a reference to the following assemblies:
1. The
Microsoft.Dynamics.AX.Application.FinancialManagement assembly found in the
VSAssemblies folder of the deployed Microsoft Dynamics AX instance—for example,
2. C:\Program Files\Microsoft
Dynamics AX\6.2\Server\AxaptaDev\bin\VSAssemblies. Make sure that the Copy
Local property is set to True.
3. The
Microsoft.Dynamics.AX.ManagedInterop assembly found in the bin folder of the
deployed Microsoft Dynamics AX instance—for example, C:\Program Files\Microsoft
Dynamics AX\6.2\Server\AxaptaDev\bin.
Add the
following using statements to the source file where you will define the
exchange rate provider.
using
Microsoft.Dynamics.AX.Application.FinancialManagement.Currency;
using
Microsoft.Dynamics.AX.ManagedInterop;
using
System;
using
System.Collections.Generic;
using
System.Globalization;
using
System.IO;
using
System.Net;
using
System.Runtime.InteropServices;
using System.Xml;
Create an ExchangeRateProviderDotNetOanda
class that is derived from the abstract
ExchangeRateProvider
base class.
namespace
DotNetExchangeRateProviderOanda
{
class ExchangeRateProviderDotNetOanda : ExchangeRateProvider
{
const string QuoteXPath = "//response/quotes/quote";
const string BidXPath = "//bid";
const string DateXPath = "//quote/date";
const string ServiceTimeoutKey = "serviceTimeout";
const string ServiceURLKey = "ServiceUrl";
const string OANDADateFormat = "yyyy-MM-dd";
const string HttpWebRequestMethod
= "GET";
const string
HttpWebRequestContentType = "application/xml";
const string
HttpHeaderAuthorization = "Authorization";
const string KeyTokenPrefix = "Bearer ";
private List<decimal> rates;
private List<DateTime> dates;
}
}
Add a GuidAttribute
attribute to uniquely identify the provider.
[GuidAttribute("FF7BF89C-18C1-42AF-91BA-07BBC3419D39")]
class
ExchangeRateProviderDotNetOanda : ExchangeRateProvider
{
const string QuoteXPath = "//response/quotes/quote";
const string BidXPath = "//bid";
const string DateXPath = "//quote/date";
const string ServiceTimeoutKey = "serviceTimeout";
const string ServiceURLKey = "ServiceUrl";
const string OANDADateFormat = "yyyy-MM-dd";
const string HttpWebRequestMethod
= "GET";
const string
HttpWebRequestContentType = "application/xml";
const string
HttpHeaderAuthorization = "Authorization";
const string KeyTokenPrefix = "Bearer ";
private List<decimal> rates;
private List<DateTime> dates;
}
Select to
automatically implement the ExchangeRateProvider abstract class.
Implement the Name property. The name used in the following
code is hard-coded; however, in production code, a label should be used to
enable proper translation. Providers can use the static GetStringFromAxLabel
method of the ExchangeRateProvider class to get a label from
Microsoft Dynamics AX.
public override string Name
{
get { return "Oanda .NET provider"; }
}
Implement the Id property. Use the following pattern to
return the GUID that is defined in the GuidAttribute attribute of the
exchange rate provider.
public
override string
Id
{
get { return typeof(ExchangeRateProviderDotNetOanda).GUID.ToString();
}
}
Implement the GetSupportedOptions method. This is the first
use of an X++ proxy, the ExchangeRateProviderSupportedOptions class. X++
proxies make it possible to instantiate and use X++ resources and logic within
.NET code. Keep in mind that this comes at a performance cost, because all
these calls are made through reflection. We recommend that you minimize the
number of calls into X++ to increase performance.
Public override ExchangeRateProviderSupportedOptions
GetSupportedOptions()
{
ExchangeRateProviderSupportedOptions
supportedOptions = ExchangeRateProviderSupportedOptions.construct();
supportedOptions.parmDoesSupportSpecificCurrencyPairs(true);
supportedOptions.parmDoesSupportSpecificDates(true);
supportedOptions.parmFixedBaseIsoCurrency("");
return supportedOptions;
}
Implement
the GetConfigurationDefaults method.
public override ExchangeRateProviderConfigDefaults
GetConfigurationDefaults()
{
ExchangeRateProviderConfigDefaults
configurationDefaults = ExchangeRateProviderConfigDefaults.construct();
configurationDefaults.addNameValueConfigurationPair("serviceTimeout", "5000");
configurationDefaults.addNameValueConfigurationPair("serviceUrl", "https://www.oanda.com/rates/api/v1/rates/{0}.xml?quote={1}&start={2}&end={3}&fields=averages");
return configurationDefaults;
}
Implement
the following helper methods. These are used only in this example and are not
required for each provider.
private
void readRate(string
_xmlString)
{
XmlDocument xmlDom = new
System.Xml.XmlDocument();
XmlNode xmlQuoteNode, xmlBidNode, xmlDateNode;
decimal exchangeRate;
System.DateTime exchangeDate;
string value;
xmlDom.LoadXml(_xmlString);
// Find the Quote
xmlQuoteNode =
xmlDom.SelectSingleNode(QuoteXPath);
if (xmlQuoteNode != null)
{
// Find the exchange rate
xmlBidNode =
xmlQuoteNode.SelectSingleNode(BidXPath);
if (xmlBidNode != null)
{
value =
xmlBidNode.InnerText;
exchangeRate = Convert.ToDecimal(value);
if (exchangeRate != 0)
{
rates.Add(exchangeRate);
}
}
//Find the date of the exchange rate.
xmlDateNode =
xmlQuoteNode.SelectSingleNode(DateXPath);
if (xmlDateNode != null)
{
value =
xmlDateNode.InnerText;
// convert the date from UTC to local timezone.
exchangeDate = System.DateTime.Parse(value,
System.Globalization.CultureInfo.CurrentUICulture,
System.Globalization.DateTimeStyles.AssumeUniversal);
if (exchangeDate != null)
{
dates.Add(exchangeDate);
}
}
}
}
Implement
the GetExchangeRates method.
public override ExchangeRateResponse
GetExchangeRates(ExchangeRateRequest
_exchangeRateRequest)
{
ExchangeRateResponse response = ExchangeRateResponse.construct();
ExchangeRateResponseCurrencyPair currencyPairResponse;
ExchangeRateResponseExchangeRate
exchangeRateResponse;
ExchangeRateRequestCurrencyPair
currencyPairRequest;
ExchangeRateProviderConfig config = ExchangeRateProviderConfig.construct();
string oandaRequestString;
DateTime currentDate;
decimal exchangeRate;
string serviceUrl;
int serviceTimeout;
WebResponse webResponse;
StreamReader streamReader;
Stream stream;
HttpWebRequest httpWebRequest;
WebHeaderCollection webCollection;
DateTime fromDate, fromUTCDate;
TimeZone localTimeZone;
int compareResult;
string XMLOut;
string dateForRequest;
//string key;
rates = new List<decimal>();
dates = new List<DateTime>();
localTimeZone =
System.TimeZone.CurrentTimeZone;
serviceTimeout = Convert.ToInt32(config.getPropertyValue(this.Id, ServiceTimeoutKey));
serviceUrl =
config.getPropertyValue(this.Id,
ServiceURLKey);
// Iterate over the requested currency pairs. This is only
required for providers
// that support specific currency pairs.
_exchangeRateRequest.initializeCurrencyPairEnumerator();
while (_exchangeRateRequest.moveNextCurrencyPair())
{
// 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 factor, the framework will make the necessary
adjustments when
// saving the exchange rates.
currencyPairResponse.parmExchangeRateDisplayFactor(ExchangeRateDisplayFactor.One);
// convert to UTC which is required by OANDA
fromUTCDate
= localTimeZone.ToUniversalTime(fromDate);
dateForRequest =
fromUTCDate.ToString(OANDADateFormat);
// Build the request URL.
oandaRequestString = string.Format(serviceUrl,
currencyPairRequest.parmFromCurrency(),
currencyPairRequest.parmToCurrency(),
dateForRequest,
dateForRequest);
// Configure the request for OANDA.
httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(oandaRequestString);
httpWebRequest.Method = HttpWebRequestMethod;
httpWebRequest.ContentType = HttpWebRequestContentType;
httpWebRequest.Timeout = serviceTimeout;
// Authentication
webCollection =
httpWebRequest.Headers;
webCollection.Add(HttpHeaderAuthorization, KeyTokenPrefix + //TODO: Retrieve and concatenate your Key from 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);
for (int i = 0; i
<= rates.Count - 1; i++)
{
currentDate = dates[i];
exchangeRate = rates[i];
if (currentDate > DateTime.MinValue && exchangeRate != 0)
{
exchangeRateResponse = ExchangeRateResponseExchangeRate.construct();
exchangeRateResponse.parmValidFrom(currentDate);
exchangeRateResponse.parmExchangeRate(exchangeRate);
currencyPairResponse.addExchangeRate(exchangeRateResponse);
currentDate = DateTime.MinValue;
exchangeRate = 0;
}
}
}
catch (WebException
e)
{
// 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.
string error = e.Message;
}
response.addOrUpdateCurrencyPair(currencyPairResponse);
rates = new List<Decimal>();
dates = new List<DateTime>();
fromDate = fromDate.AddDays(1);
compareResult = fromDate.CompareTo(_exchangeRateRequest.parmToDate());
}
}
return response;
}
Add the new
project to the Application Object Tree (AOT).
In the properties
of the project, select to deploy the project to the client and the server.
Right-click
the project, and then click Deploy. This will deploy the assembly to the
server and client so that it is ready to be used by Microsoft Dynamics AX.
The project
can now be found under the Visual Studio Projects node of the AOT. It
can be opened by right-clicking and then clicking Edit.
Restart the AOS service if the
provider is not available when you attempt to register exchange rate providers
in the application. Hot swapping should make it unnecessary to do this when you
make updates to the assembly; however, when you first add the assembly, a
restart may be necessary.
The following classes and
methods are important areas of the framework that are handy to understand when
you debug issues:
1. ExchangeRateProviderFactory.initialize() – This method creates
instances of the exchange rate providers, and is called when exchange rates are
registered or imported. If you cannot get your provider instantiated, start
debugging here.
2. ExchangeRateProviderRegistration.initialize() – This method searches for
providers so that they can be registered by the application. If you cannot see
your provider in the registration form, start debugging here.
3. ExchangeRateImportOperation.import() – This method drives the
import process, which involves calling the necessary provider and storing the
exchange rates.
4. ExchangeRateProviderConfig – This class provides access to configuration information
for the providers.
Thanks for comments.....