Skip to content
Archive of posts tagged ax2012

Hodge-podge – 06/2013

AxErd

Microsoft published several entity-relationship diagrams (ERD) regarding AX2012 R2 (+ some related data such as foreign keys). Direct link here: AxErd.

Dynamics AX 2012 and TFS 2012

Dynamics AX 2012 (CU5/R2 CU1) is officially compatible with Team Foundation Server 2012 (source).

Code maps debugger integration

Code maps inVisual Studiu 2012 (CU1+) serve for visualization of code. Version CU2 added the integration to debugger – all you have to do is to activate Code maps and you immediately get visual idea of where you are in the code. See this short video. Unfortunately this is included only in the Ultimate version.

Microsoft Virtual Academy

If you don’t know the virtual akademy, I recommend taking at least a look. Although it’s quite focused on administrators, developers find also a lot of interesting stuff. I can recommend recent jumpstarts about Windows 8 development and Team Foundation Server, for instance.

Conference: Agile Prague 2013

Agile Prague conference is going to be as late as in September, but you can get a discount if you register soon enough. Details on agileprague.com/registration.htm.

Convergence of Skype and Lync

From now on, Skype users can send IM and call Lync users – and vice verse. More details here:

Twitter

I’ve opened an account on Twitter – I’m going to try it for few months and then I’ll see how much I find it useful. I’m there as @goshoom – feel free to follow me.

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.

Empty startupCmd in AX2012

Recently I had some troubles with one AX2012 environment – startup commands simply didn’t work. AX client started, but that was all.

Debugging showed that startup() method in Info class didn’t receive any command text at all:

After some more investigation I noticed that it does work if I use another configuration file:

The difference between those two configurations is here:

logdir,Text,%USERPROFILE%\Microsoft\Dynamics Ax\Log\
company,Text,
partition,Text,
startupcmd,Text,
extracmdline,Text,-Development
startupmsg,Text,

If extracmdline,Text,-Development (which opens AX directly in Development Workspace) is included, startup commands are ignored. If extracmdline is empty, everything works as usual. It behaves in the same way in both AX2012 and AX2012 R2.

It’s easy to avoid such configuration files… as soon as you know that you should.

X++ to CIL

Dynamics AX 2012 is able to generate Common Intermediate Language from X++. That allows much more code running in Common Language Runtime and switching between X++ runtime and CLR can be greatly reduced, both of which have positive impact to performance.

Many developers see it as a bit of black magic, which sometimes causes avoidable difficulties. That’s why I would like to share few findings. This article describes some basic terms and where the CIL code generated from X++ is stored. Later I want to write more about the actual translation from X++ to CIL.

CIL

CIL (Common Intermediate Language), formerly known as MSIL (Microsoft Intermediate Language) or sometimes simply IL is a low-level language used by .NET Framework (and Mono). The runtime (CLR) doesn’t work directly with high-level languages like C#, it works with CIL and compilers of high-level languages simply generate CIL. The runtime handles CIL in the same way regardless it was generated from C#, Visual Basic, IronPython or from X++.

When a piece of CIL code is used for the first time, CLR optimize it for the particular processor a compiles it to native code.

Generating CIL from X++

Some X++ code is run as CIL by default (batch jobs, web services) and you can explicitly run other X++ code in CIL too (see X++ Compiled to .NET CIL). It’s always server-side code – the generated CIL is not available on client. Not all X++ code can be transformed to CIL – only types (classes, tables and enums) are supported, the code mustn’t use runbuf() method and so on.

To generate CIL, you typically use Generate Incremental CIL or Generate Full CIL buttons in the development workspace.

Assemblies and modules

As any other CIL code, CIL generated from X++ is saved in an assembly. Assemblies are basic building blocks of .NET applications grouping types together, providing additional information (manifest), versioning and so on. They may take form of an executable file (.exe) or a class library (.dll).

But unlike most assemblies, the assembly generated from X++ code consists of a high number of files. The DLL file doesn’t contain any types – it merely refers to other files known as modules, and all these files together make up a single logical unit. The advantage of this approach is that a module is loaded only if any type from that module is needed. As you’ll see, there is a lot of code generated from X++, so it makes a big difference if only a small part of it must be loaded.

Dynamics.Ax.Application

The assembly with CIL code generated from X++ can be found in bin\XppIL folder on AOS (for example c:\Program Files\Microsoft Dynamics AX\60\Server\MyAos\bin\XppIL). It consists of these files:

File Description
Dynamics.Ax.Application.dll Contains links to modules and information about where to find particular types.
Enums.netmodule Contains CIL enums generated from X++ enums.
InterfaceTypes.netmodule Contains interface types (e.g. Enumerable).
KernelTypes.netmodule Contains types defined under System Documentation node in AOT. Note that some important types are already defined as CLR types in other assemblies and are not generated from X++.
Dynamics.Ax.Application.dll{n}.netmodule
where {n} ∈ <0, 999>
These modules contain all other X++ types (i.e. classes and tables, including table maps and views).The total size is about 200 MB, so it’s really important that we don’t have to load everything to memory.

Decompiling

One important advantage (and sometimes disadvantage) of CIL code it that it’s relatively readable and it can be decompiled back to high-level languages, which is great for examining existing code and learning how exactly it works. In this case, you can see how your X++ is translated to CIL (note that most of AX kernel is written in native code, so this doesn’t apply to it).

You can use IL Disassembler to get CIL code from assemblies. To decompile CIL back to C#, use Reflector, for instance (unfortunately,  popular open-source program ILSpy doesn’t support multi-module assemblies).

To examine a specific type, you have to know in which module it can be found. One option is to look into the manifest in Dynamics.Ax.Application.dll – it contains references like this:

.class extern public Dynamics.Ax.Application.Dialog
{
    .file Dynamics.Ax.Application.dll11.netmodule
    .class 0x02000025
}

Use text search to find the requested type; you can see module name in the .file property.

Or you can follow the way used by AX for distributing types to modules. Take an object ID, divide it by 10, round it down, take last three digits, remove leading zeros and use this number in name of the .netmodule file. For example: AifExceptionMap table has ID 100029. After dividing by ten and rounding down, you get 10002. Last three digits are 002, therefore AifExceptionMap can be found in Dynamics.Ax.Application.dll2.netmodule file. Of course, this is an implementation detail that may change at any time (the number of modules is even configurable), but knowing it surely can’t harm.

AX2012 Windows 8 App Developer Starter Kit

Microsoft just launched an open-source project called Dynamics AX 2012 Windows 8 App Developer Starter Kit. As its name suggests, the project is about developing Windows 8 applications that communicate with Dynamics AX 2012.

It currently contains one sample application and a guide describing its development. The sample application displays AX alerts for a given user – it looks like this:

The application uses just AIF and Windows Store project in Visual Studio – the Starter Kit is (at least in this moment) about demonstrating how to develop Windows 8 apps, not about implementing any special libraries for AX-Win8 projects.

According to information on Codeplex, Microsoft plans to add additional sample applications and tutorials in future.

I successfully followed the guide and built the sample application using AX2012 R2, TCP adapter (the guide uses HTTP) and Visual Studio 2012 Express. The only trouble was that the guide expects that the type of WorklistService.EventAlertForUserServiceFindResponse.EventAlertForUser property is AxdEventAlertForUser, while it was generated for me as AxdEntity_EventInbox[], so I had to adjust some code accordingly.

Update: Because there is a problem with downloading the files from Codeplex, you can temporarily download them directly from here: