Parsing XML files with .NET Interop

Today I want to show how to download XML files from internet, validate, parse and use them in Dynamics AX 2012. It’s also yet another example of how we can simplify implementation by using appropriate .NET APIs and seamlessly integrate such a code with Dynamics AX.

Our data source will be daily exchange rates provided by European Central Bank as a simple XML file: www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml. In the end, we’ll have the data in an X++ object and we’ll display them in infolog:

You can download the complete solution here – it contains a single .xpo file (for AX2012). I’m going to discuss most of the code below, but you’ll have to look to the source code for details.

The very first step is to create a C# class library and add it to AOT. I’ve called it EcbExchRates and set the namespace to Goshoom.Finance.

Then create a new class – DailyExchangeRateService – and add few constants that will be needed in a minute.

private const string DailyRatesUri = "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
private const string GesmesNamespace = "http://www.gesmes.org/xml/2002-08-01";
private const string EcbNamespace = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";

The content of the XML file must be downloaded somehow – that’s responsibility of the DownloadData() method.

private string DownloadData()
{
    using (System.Net.WebClient webClient = new System.Net.WebClient())
    {
        return webClient.DownloadString(DailyRatesUri);
    }
}

We need to parse the string returned by DownloadData(). Fortunately, .NET offers several APIs helping with that – let’s use LINQ to XML in this case. First load the XML string to XDocument (from System.Xml.Linq namespace) and store it in an instance variable:

XDocument xmlData;
 
private void Load()
{
    xmlData = XDocument.Load(new StringReader(this.DownloadData()));
}

One of many advantage of XML is the support for validation against XSD schemas – we’re doing that in the Validate() method. Schemas are stored as embedded resources (you can see them in the downloadable source code).

private void Validate()
{
    XmlSchemaSet schemas = new XmlSchemaSet();
 
    schemas.Add(GesmesNamespace, XmlReader.Create(new StringReader(Resources.GesmesSchema)));
    schemas.Add(EcbNamespace, XmlReader.Create(new StringReader(Resources.EcbSchema)));
 
    xmlData.Validate(schemas, null);
}

The last step is to parse the XML to an object. But before we can do that, we have to define a class for the object. We’ll create a simple X++ class holding the date and exchange rates:

class DayExchRates
{
    date    effectiveDate;
    Map     rates; // Currency code -> exchange rate
 
    public void addRate(str _currency, real _rate)
    {
        rates.insert(_currency, _rate);
    }
 
    …more code…
}

Drag the class from Application Explorer to your C# project so the .NET proxy gets generated.

Now we can extract data from the XML – first we need to find the right element. Except a minor complication caused by namespaces, the implementation is trivial:

XNamespace gesmesNs = GesmesNamespace;
XNamespace ecbNs = EcbNamespace;
XElement ratesRoot = xmlData.Element(gesmesNs + "Envelope").Element(ecbNs + "Cube").Element(ecbNs + "Cube");

We create an instance of our X++ class and get the date from the time attribute:

DayExchRates rates = new DayExchRates();
rates.parmEffectiveDate((DateTime)ratesRoot.Attribute("time"));

Then we iterate through nodes with rates, get currency code and exchange rate from attributes and add them to the X++ class.

foreach (XElement rateNode in ratesRoot.Descendants())
{
    rates.addRate(
        rateNode.Attribute("currency").Value,
        decimal.Parse(rateNode.Attribute("rate").Value));
}

Encapsulate parsing in Parse() method and put it all together in a public method called GetRates():

public DayExchRates GetRates()
{
    this.Load();
    this.Validate();
    return this.Parse();
}

Display properties of the Visual Studio project, set Deploy to Client to Yes (because our test X++ code will run on client), deploy the project and we’re done in Visual Studio.

Create a job in Dynamics AX and put the following code into it (just change names, if you used different ones):

Goshoom.Finance.DailyExchangeRateService service = new Goshoom.Finance.DailyExchangeRateService();
DayExchRates dailyRates = dailyRates = service.GetRates();

The code is very straightforward, but there is one thing that deserves attention. What the GetRates() method returns is not the managed proxy class – .NET Interop is smart enough to use replace the proxy by the original X++ type, therefore we can directly assign the value from the .NET method to a variable of our X++ type. That may not sound like a big difference, but in fact it greatly simplifies things.

To complete our task, let’s display exchange rates in infolog. This is just a normal X++ code:

MapEnumerator enumerator = dailyRates.getRates().getEnumerator();
 
while (enumerator.moveNext())
{
    info(strFmt("%1: %2", enumerator.currentKey(), num2str(enumerator.currentValue(), -1, 4, -1, -1)));
}

You probably could do the same thing in pure X++ (I would just have to investigate how to validate against two target namespaces in X++), but you would get much more code to write, read and maintain, which would negatively affect your productivity. This difference would be even bigger if the example was not so simple.

Dynamics AX/X++ simply can’t – and shouldn’t – duplicate all libraries and APIs available in .NET, but that doesn’t mean that we have to do without them. .NET Interop allows us to combine the best from both worlds (AX and .NET) and each version of AX makes it a bit easier.