Skip to content

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.

Layer change in MorphX VCS

I already posted an article about the same thing some time ago: Moving a model to another layer. The difference is that it was about Team Foundation Server, but this time I needed to move a model to another layer while using MorphX VCS.

MorphX VCS doesn’t support synchronization, therefore my original approach can’t be used. What you need is to move all objects to the target layer by export/import and update data in tables that form MorphX VCS repository. The following code will help you with the latter step:

str oldLayer = 'usr';
str newLayer = 'cus';
str 10 searchPattern = strFmt(@'\\%1\\*', oldLayer);
 
SysVersionControlMorphXItemTable item;
SysVersionControlMorphXRevisionTable rev;
SysVersionControlMorphXLockTable lock;
 
str replaceLayer(str path)
{
    return strFmt(@'\%1\%2', newLayer, subStr(path, 6, strLen(path)));
}
 
ttsBegin;
 
while select forUpdate item
    where item.ItemPath like searchPattern
{
    item.ItemPath = replaceLayer(item.ItemPath);
    item.update();
}
 
while select forUpdate rev
    where rev.ItemPath like searchPattern
{
    rev.ItemPath = replaceLayer(rev.ItemPath);        
    rev.update();
}
 
while select forUpdate lock
    where lock.ItemPath like searchPattern
{
    lock.ItemPath = replaceLayer(lock.ItemPath);        
    lock.update();
}
 
ttsCommit;

Code in forms

Almost every Dynamics AX developer knows them rule that business logic shouldn’t go to forms, but only few take it at least a bit seriously. It’s surprising that although developers complain about slow and hard to maintain forms, it doesn’t induce then to do something differently.

I believe it has several reasons:

  1. Programmers often don’t understand exactly what problems application logic in forms brings. So they know the rule, but they don’t understand it.
  2. Programmers don’t know how else to structure code. They know the rule, but they can’t apply it.
  3. Or they actually don’t care about quality.

Problems with code in forms

Form is copied to layer as a whole

In AX2009 and older version the form is copied to the active layer as soon as you change even a single property or method. That causes enormous troubles for upgrades and code merging. For example: if you change one method in CUS layer and another method changes later in VAR layer, the change doesn’t appear in CUS layer until you manually upgrade it. If the same happened in a class, no upgrade would be needed, because individual methods (not the whole object) are copied to higher layers.

Fortunately AX2012 has significantly reduced this problem – forms are copied in smaller parts, not as complete forms, but data sources still suffer from the same problem.

Form is not type

Form in Dynamics AX is not type (as classes and tables, among others), it’s in essence just somehow assembled instance of FormRun class. Therefore you can’t reference it as type, which would be often useful.

For example, you can’t use forms with is and as operators. Or if you want to call a form method from another object, compiler can’t check whether the method exists, which easily leads to errors. By the way, such calls are also not taken into account in cross-references and form methods can’t be referenced by functions such as methodStr() either (e.g. in setTimeOut() calls), therefore compiler again can’t verify their existence.

See more about method calls in my article Dynamic method despatch in X++.

Reusability of code

As already mentioned, code written in a form is difficult to call safely from other objects, therefore every form should contain only the code that’s related directly to it and doesn’t represent any more general concept. All code that could be useful even somewhere else should be moved to classed, or alternatively to tables, table maps or so.

Inheritance

Because form is not individual class, you can’t create its child and inherit behavior. Nevertheless that would be often very useful. For example:

  • A single form offering different behavior for different data context. That’s implemented by a class with specialized children. See PurchTable form and its controller classes PurchTableForm_Project and PurchTableForm_Journal, for instance.
  • In many cases you can minimize changes in code of an existing class if you create its child and implement changes of behavior there. You’ll then change a single method in the parent class – you’ll set construct() to return instance of your class. Because no other methods were changed, they can’t cause layer conflicts during upgrade.
  • A child of the class may be used in unit testing to override some methods (e.g. to simulate a data source) or to provide access to instance variables and protected methods.

If you have code in a class, creating a child is trivial. In case of a form, you either have to do without the advantages described above, or you have to first refactor the code to a class.

Interfaces

I often see forms that do something like this:

Object caller = element.args().caller();
if (caller.name() = formStr(Form1)
    || caller.name() = formStr(Form2)
    || caller.name() = formStr(Form3))
{
    caller.doSomething();
}

This code basically says that the enumerated forms support some behavior represented by doSomething() method. If we want add, remove or rename a supported form, it’s necessary to change this code, with all consequences such as copying the form to the active layer etc.

Normal object-oriented code would use an interface and it wouldn’t need the enumeration of supported objects at all. Unfortunately forms are not classes and can’t implement interfaces. But you can pass an instance of the form controller class in Args.caller() or Args.parmEnum() and work with it instead of with the form.

DoSomethingProvider provider = element.args().caller() as DoSomethingProvider;
if (provider)
{
    provider.doSomething();
}

Testing

For any automated tests, it’s much easier and faster to work with classes and methods than working with the whole form, searching for controls in the form, reading their properties etc. For example, instead of finding Post button in a form and call its click() method, it’s easier to simply call post() method in the controller class.

Even for UI tests it’s sometimes useful to replace the class containing the real application logic by some other implementation – it may read test data from another data source, log information about test runs and so on.

Code on client

All form methods run on client. If you, say, send a request from client to database, it’s sent to AOS, AOS sends it to database, the database sends response to AOS and AOS finally passes it to client. Note that every connection requires some overhead, depending especially on network latency and amount of data to transfer.

Ignoring this fact is the most common reason for performance problems with forms, because if you’re not careful, it’s easy to generate a lot of requests and transfer significant amount of data.

Detailed discussion would be beyond the scope of this post, but basic rules can be summarized like this:

  1. Minimize number of calls between individual tiers (client/AOS/database). If you need several requests to database or server objects, don’t send each individual call from client. Use a server-bound method to call everything needed from AOS and returns only the result, so there will be only one call to and from server.
  2. Don’t try to move everything to AOS – if your code doesn’t need anything from server, leave it running on client.
  3. Use cache. It’s especially important for display and edit methods.

Solution

Controller class

Writing code for forms in classes is not much harder than doing it directly in forms. You just have to create a new class (by convention, the class is named as the form + Form suffix, e.g. SalesTableForm), create its instance in the form and pass into it references of all objects that you’ll need. Then everything is almost as usual, you just create methods in the new class instead of in the form.

If you work in lower versions than 2012 and your solution may be extended in higher layers (e.g. by end-user in USR layer), I recommend to create such a class as soon as you need to add any code to the form. In other cases, you may begin with code in form (unless you need to call it from other objects or so), but you must be ready to refactor it to class as soon as it gets a bit more complicated.

The controller class is tightly bound to the form, therefore it must run on client. Code that should run on server needs to be implemented in other classes or alternatively in server methods.

Surprisingly often developers ignore even already existing classes and write code directly to forms. Here the solution must be quality control and training of developers.

Refactoring

Refactoring of forms with lots of code is often quite laborious, but it’s usually good investment (in comparison with long-term problems with maintainability, performance etc.) The main problem is that it’s usually done only after the complexity of the form logic exceeded what the development team is able to maintain.

In the first phase, create a controller class and move code into it without much effort to change its structure. This is the most difficult phase, because you have to deal with all reference to automatically declared variables (form controls, data sources), you have no idea which methods are called by other objects, you don’t have sufficient support in cross-references etc.

As soon as you have code in the class, set method access modifiers (private and so on) as restrictively as possible – it will show you what you can safely refactor and where you have to take other objects into account. The second phase is about dividing code to multiple classes according to their responsibilities, requirements (e.g. server-bound), usage of inheritance and so on.

Hodge-podge – 04/2013

This new “series” is intended to briefly mention various topics that I find interesting and/or useful, but they’re too short for a separate posts. I don’t currently want to spend more time with them or I just simply want to give you a link and I wouldn’t add any additional value.

Compare 〈Dynamics AX〉

The Compare feature for application objects in Dynamics AX isn’t particularly smart. It’s useful simple cases, but it fails to understand more complicated changes – it shows them as two completely different things, so you have to identify exact changes by yourself.

If I need a more reasonable comparison, I use external tools such as TortoiseMerge.

It might be useful to integrate it into AX. All what would be needed is to export the texts that we want to compare to temporary files and call TortoiseMerge with right parameters.

I also often use comparison directly in source control systems (TFS, SourceSafe).

BindableBase 〈Windows Store App〉

As I already mentioned on this blog, Caller Information in .NET 4.5 helps to simplify implementation of INotifyPropertyChanged interface. BindableBase class in development for Windows Store uses the same approach and makes it all even simpler.

BindableBase is added to your Visual Studio project (Common folder) with any more advanced page than Blank Page. To create an observable class, you just need to inherit from BindableBase, call SetProperty() in the property setter, pass the field by reference and the value to be set. BindableBase will do the rest. For example:

public class Person : BindableBase
{
    private string name;
    public string Name
    {
        get { return name; }
        set { SetProperty(ref name, value); }
    }
}

Windows 8 commands via Remote Desktop 〈Windows 8〉

If you’re connected via Remote Desktop to Windows 8 (and I expect the same with Windows Server 2012), you can a special menu for Windows 8 functions such as displaying Charms.

In full screen view, you’ll find the menu on the top among other functions for remote desktop.

In windowed mode, right-click the window title:

Volume licenses for external subjects 〈Microsoft Volume Licensing〉

This is a common question and I simply copied the answer from Frequently Asked Questions About Product Licensing.

Question: In our company we have onsite contractors who work on short-term projects. Can we assign Microsoft product licenses (Office, CALs, etc.) which we purchased through our own Volume Licensing agreement to these contractor-owned devices so they use our licensed software for our projects?

Answer: Yes, as long as those licenses are used for the benefit of your company, the licensee, you can assign your licenses to third party devices.

You are limited in how often you can assign your licenses. Volume Licensing product licenses can only be reassigned to other devices every 90 days, not more frequently. If the software will be used for the benefit of the contractors and not your organization then the contractor needs to purchase their own licenses or optionally explore other types of short-term software subscription licenses.

Useful modules 〈Powershell〉

Creating Powershell modules is very simple and a lot of people already did it. But do you know what’s available? Try this list of Popular PowerShell Modules. I’m going to take a closer look at OData PowerShell Explorer, for instance.

Tracing with EventSource in .NET 4.5

.NET 4.5 introduced a new way of creating trace events. Such events are created by applications when something interesting happens and they’re especially useful for diagnostics, both functional and performance. Tracing is not a new idea, of course, and many solutions exist even in older versions of .NET framework (e.g. TraceSource). But the new solution offers new capabilities, it’s very easy to use and unlike many other means, it can be used by Windows Store applications.

The relevant types reside in System.Diagnostics.Tracing namespace and the most important class is EventSource. EventSource is what raises events, but you can’t use it directly – you have to create a child class with event definitions, nevertheless EventSource does all the difficult work.

This is a trivial example of a custom EventSource:

MyOwnEventSource : EventSource
{
    public static MyOwnEventSource Log = new MyOwnEventSource();
 
    public void TaskStarted(string taskName)
    {
        // Call EventSource.WriteEvent() to do all the work
        WriteEvent(1, taskName); // "1" is event ID
    }
}

The event can be than raised by a simple call like this:

MyOwnEventSource.Log.TaskStarted("myTask");

Method names and parameter types and names are important – event listeners will get information about them and you’ll see them in event logs. Also note that only some parameter types are supported: primitive types, strings, Guid and enums. If you used any other type, tracing wouldn’t work.

You can also define event severity, category and such things. It helps to categorize events and listeners may ask only for events with some minimum severity, for example. Simply decorate an event with EventAttribute and set properties as suitable. They include, for instance:

  • Level – severity such as Error, Verbose etc.
  • Message – formatting string to convert an event to a human readable text.
  • Keywords – define groups an event belongs to. You can define your own keywords by creating a nested class called Keywords that will contain constants of EventKeywords type (the same pattern applies also to other properties).
  • EventId – you have to pass the right eventId to WriteEvent() and if you don’t set it in EventAttribute, system will assign event ID 1 to the first method, 2 to the second method and so on. If you added a new method on top, subsequent events would be renumbered and send wrong IDs to WriteEvent(). Therefore it’s safer to explicitly assign event ID in EventAttribute. Note that event ID must be unique per trace source.

Example:

[Event(4, Message = "Item skipped: {0}", Level = EventLevel.Warning, Keywords = Keywords.Diagnostic]
public void ItemSkipped(string reason){ WriteEvent(4, reason); }

All methods with void return type are considered events. You can exclude some methods by decorating them with NotEventAttribute. Such methods can have any parameter types, so you can even use them as convenient overloads that accept complex types, convert them to primitive types and call actual events.

[NotEvent]
public void ItemSkipped(Exception ex) { ItemSkipped(ex.ToString()); }

One more useful attribute is EventSourceAttribute – it allows you to set event source’s name. The default name is the class name without namespace, which is often not suitable.

[EventSource(Name = "Goshoom.TestApp")]
class Logger : EventSource

My last tip is that you should check if the event source is enabled before doing anything else, to minimize performance impact if it’s not active. An event will then look like this:

public void ItemSkipped(string reason)
{
    if (IsEnabled())
    {
        WriteEvent(4, reason);
    }
}

Consuming events

You need a tool capable of working with ETW events – my example uses PerfView.

Start PerfView and choose Collect > Collect to explicitly start and stop collection.You could also use Collect > Run to run and trace a specific application, but you need Collect if you work with Windows Store applications.

Configure data to collect and add your event source to Additional Providers. It must be prefixed by star (*). See the picture for an example.

Click Start Collection button, perform all actions you want to trace, return to PerfView and click Stop Collection.

When the log file is ready, open the Events view:

It shows all logged events and you can easily filter them, choose columns to display, see data associated with the event, time of the event and so on.

The picture shows events filtered by name and details of one particular event. You can see that the event source name is Goshoom.TestApp and the event name is ItemSkipped. Column reason was generated based on the parameter of ItemSkipped() method.

Both EventSource and PerfView have much more features than I showed here, but hopefully it gave you some idea. To learn more, try some links that I found useful:

Specification for EventSource class
Vance Morrison’s Weblog
PerfView tutorial on Channel 9