Skip to content
Archive of posts tagged ax2012

XML DocType in X++

Let’s say that we want to use X++ to create an XML file with the following DocType definition:

<!DOCTYPE MyType SYSTEM "http://www.validome.org/check/test.dtd">

AX has Xml* classes (e.g. XmlDocument) for such purpose, but let’s build it with .NET classes first (you’ll see why in a moment). This is X++ code calling .NET classes through .NET Interop:

System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', null, 'http://www.validome.org/check/test.dtd', null));
info(xmlDoc.get_OuterXml());

The output is exactly what we want.

Now let’s try to rewrite it with native X++ classes:

XmlDocument xmlDoc = new XMLDocument();
xmlDoc.appendChild(xmlDoc.createDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', ''));
info(xmlDoc.xml());

Notice that it’s very similar to the previous example, but we had to replace null values with empty strings (”), because X++ doesn’t support null-valued strings.

The output is not correct in this case:

<!DOCTYPE MyType PUBLIC "" "http://www.validome.org/check/test.dtd"[]>

We can easily prove that the empty strings are to blame by rewriting our .NET-based example to use empty strings instead of nulls:

System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', ''));
info(xmlDoc.get_OuterXml());

The output is identical to what we get from X++.

Let me explain what’s going on.

If you provide a value for publicId (the second parameter), the PUBLIC keyword is used, followed by the URI provided in publicId. That applies to empty URI as well, so we get:

<!DOCTYPE MyType PUBLIC "" (…)>

If you want to get SYSTEM keyword, publicId must be null, not “”. The fourth parameter, internalSubset, is a similar case.

Even if you aren’t familiar with .NET at all, you likely understand the difference between a null reference (x = null) and an empty object (x = new Object()). Unlike in X++, strings in .NET are objects (instances of System.String class) and therefore they can either refer to a value or be without any value at all (= null).

Now what we can do to set null values to XmlDocument.createDocumentType()? Well… nothing. All parameters are X++ strings and null isn’t a valid value for them. It would have to be designed differently, e.g. using strings wrapped in objects or providing an additional flag to say that the value should be ignored.

The workaround is simple – you can use .NET classes (from System.Xml namespace) as in the first code snippet.

Tested in AX 2012 R3.

Operand types are not compatible

I managed to produce a really confusing error in AX 2012. Look at the following piece of code:

MyExtendedDataType a, b;
a = b;

Pretty boring, isn’t it? There is nothing to fail… except that I got a compilation error: Operand types are not compatible with the operator. What?

Actually it become quite clear as soon as I simplified my code to this trivial example. There is clearly nothing wrong with my code, it must have something to do with the data type.

To make a long story short, I created a new extended data type of type enum and forgot to fill in the EnumType property. Linking the EDT to an enum instantly solved the problem.

But beware – the compiler won’t detect many other uses of this incorrectly-defined type. For example, MyExtendedDataType a = NoYes::Yes; is compilable X++ code even if MyExtendedDataType has empty EnumType.

Compile backwards

Today I noticed that Type hierarchy browser in AX 2012 R3 allows you to compile types backwards:

HierarchyBrowserCompile

Context menu in AOT supports only forward compilation.

It’s possible that the feature is there for some time and it just took me long time to notice it, because I normally don’t compile code from Type hierarchy browser.

Merging concurrent changes in AX with TFS

Suppose you’re an AX developer working in an AX instance shared by few other developers. You want to do some changes that would likely cause issues to your colleagues until they’re finished, therefore you’ve created a separate instance and work there. To simplify the example, assume that you’ve changed only a single method.

If nobody changed it in the meantime, it’s all good – you can simply import the method. But if somebody changed it, importing your code is a bad thing, because you would overwrite somebody else’s code. It doesn’t mean just that a feature would disappear; you would probably remove only a part of a feature and the behavior of the remaining application would be unpredictable.

What you have to do is to merge your changes with the current version of the application. In the simplest case, you want to keep all changes, therefore you’ll add your changes to what’s already there. Sometimes you want to replace a previous change (because, for example, somebody made a quick fix before your more robust solution gets ready). Sometimes you’ll have to implement completely new code to allow concurrent changes to work together. In the worst case, the changes are not compatible and can’t be merged together at all. (This usually reveals a problem with project management.)

My experience indicate that the main problem is not that developers fail to merge code correctly – they often don’t do it at all and overwrite previous changes. Unfortunately you can’t avoid merging if you want to support parallel development on the same objects, nevertheless you CAN ensure that people don’t skip merging.

The answer is obviously a version control system. If you take an object from a repository, modify it and you’re going to commit it, the version control system checks whether there aren’t already conflicting changes. If there are any, you’re not allowed to continue without resolving conflicts.

Let’s take a look at a concrete example with a few pictures. I’m going to simulate two developers working on AX2012 with Team Foundation Server. Each developer has his own AX instance pointing to the same team project and branch.

To make it easier, I used a single instance and I simulated the other developer by adding some change directly in Visual Studio, using a different TFS workspace. I took an existing, empty job and checked it out to both workspaces. In Visual Studio, I typed in // Code added in Visual Studio and checked the change in. In Dynamics AX, my change was // Code added in AX. When committing changes, TFS detects a conflict and shows a dialog where you can see and merge changes:

MergeDialog

This is a very simple example, nevertheless you can see that is shows the changes I’m checking in, the changes done by others in newer versions and the resulting merged code (on the bottom). If there are several conflicts, I can resolve them one by one or simply accept one of versions of the files: ResolveAll

I can change the code in the bottom pane as needed. For example, I decided to merge comments by creating a completely new one:

ManuallyMerged

The code inserted to TFS is the result of this merge, of course:

ViewChanges

You may not like that the dialog uses the format of .xpo files and not a visual tree as in AX, but that’s how AX code and metadata are saved to source control systems and it’s usually better for merging anyway. By the way, .NET code is stored and compared exactly in the same way – as text files; it just has prettier formats. Don’t be too afraid of the .xpo format – it’s simple and you’ll need it in a few other cases, such as when reviewing changes line by line with annotate:

Annotate

It’s also worth mentioning that you may want to have a separate branch for each developer and merge code on branch-level rather than changeset-level. An advantage for merging is that it happens independently on AX, therefore you can use latest tools, regardless what version of TFS is integrated to AX. On the other hand, you usually want to integrate code together (and find any issues caused by that) as soon as possible, which is an argument against too many branches.

Data contract serialization from X++

You can decorate classes in AX2012 with DataContractAttribute in much the same way as in .NET with System.Runtime.Serialization.DataContractAttribute. The same is true also for DataMemberAttribute.

This is a simple data contract class in X++:

[DataContractAttribute]
class ItemContract
{
    str name;
 
    [DataMemberAttribute("Name")]
    public str parmName(str _name = name)
    {
        name = _name;
        return name;
    }
}

In .NET, you can serialize data contracts to XML (and deserialize from XML) with DataContractSerializer. What if you want to do the same with data contracts in X++?

I’m not aware of any such serializer in X++. You could write one, but it wouldn’t be a five-minute job. But there is a way how to actually use the .NET serializer.

As you surely know, AX2012 is able to compile X++ types to the Common Intermediate Language (CIL). Other “.NET” languages are compiled to CIL as well, which allows all of them to work together seamlessly. You can save a class written in a different language to a variable, call its methods, inherit from it and so on. (Don’t worry too much about differences between .NET, CLR, CIL, CLI etc. You just need to know that it’s all related to the same thing, usually called .NET).

When AX2012 generates CIL, .NET types for X++ classes and tables are created in Dynamics.Ax.Application assembly. For example, PriceDisc class is turned to Dynamics.Ax.Application.PriceDisc. If a piece of X++ code is executed in a CLR session, CIL types from Dynamics.Ax.Application are transparently used instead of original X++ types.

However, classes decorated by DataContractAttribute won’t be decorated by Dynamics.Ax.Application.DataContractAttribute in CIL. The X++ attribute is actually replaced by System.Runtime.Serialization.DataContractAttribute, therefore you’ll get a proper .NET data contract that can be serialized by DataContractSerializer. That’s likely what happens somewhere in AX kernel when you pass a data contract with parameters from AX to a report server, for instance.

By the way, I really like this approach. People from Microsoft managed to provide an easy way to define data contracts in X++ and still benefit from everything related to data contracts in .NET. Now it looks like an obvious solution, but who would have thought it about before?

Nevertheless what if we want to use the serializer from our own X++ code? First of all, let me introduce a C# method that will do all the serialization. All we have to do is to pass a data contract and we’ll get back the object serialized to XML.

using System;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
 
namespace Demo
{
    public class ContractSerializer
    {
        public static string Serialize(Object o)
        {
            Type type = o.GetType();
 
            if (!Attribute.IsDefined(type, typeof(DataContractAttribute)))
            {
                throw new ArgumentException(String.Format("{0} is not a data contract", type.FullName));
            }
 
            using (var stringWriter = new StringWriter())
            using (var xmlWriter = new XmlTextWriter(stringWriter) { Formatting = Formatting.Indented })
            {
                new DataContractSerializer(type).WriteObject(xmlWriter, o);
                return stringWriter.GetStringBuilder().ToString();
            }
        }
    }
}

You can put the class to a class library, add it to AOT and deploy it to both client and server.

Then we need to ensure ourselves that our X++ code will run in CIL. We’ll utilize SysOperation framework for that purpose:

class SerializationDemo extends SysOperationServiceController
{
    public static void main(Args args)
    {
        SerializationDemo demo = new SerializationDemo();
 
        demo.parmClassName(classStr(SerializationDemo));
        demo.parmMethodName(methodStr(SerializationDemo, serializeToInfolog));
        demo.parmExecutionMode(SysOperationExecutionMode::Synchronous);
 
        demo.run();
    }
 
    [SysEntryPointAttribute]
    public void serializeToInfolog()
    {
        if (!xSession::isCLRSession())
        {
            throw error("Must run CIL!");
        }
        // Here we'll do the job
    }
}

Do not omit the verification that we’re really in a .NET session. If the code ran in X++, it wouldn’t work, because we can’t simply pass an X++ object to a .NET method. If the code don’t execute in CIL, go to Tools > Options > Development and tick Execute business operations in CIL. Also, don’t forget to generate CIL.

If we’re in a .NET session, we can simply create an instance of a data contract class and send it to our method. Let’s create some data:

private DirPersonInfoData getPersonData()
{
    DirPersonInfoData person = new DirPersonInfoData();
 
    person.parmPersonName("John Doe");
    person.parmPersonPrimaryEmail("john(at)doe.com");
    person.parmPersonUserId("jdoe");
 
    return person;
}

Unfortunately, if you try to pass it directly to the serialization method:

DirPersonInfoData person = this.getPersonData();
Demo.ContractSerializer::Serialize(person);

it will fail with the error “Microsoft.Dynamics.AX.ManagedInterop.Object is not a data contract”.

The problem here is that the generated code uses a proxy class, which is not what we need, because proxy classes are not generated with attributes. Let’s tell AX that we’re working with a .NET object:

DirPersonInfoData person = this.getPersonData();
System.Object clrPerson = CLRInterop::getObjectForAnyType(person);
;
Demo.ContractSerializer::Serialize(clrPerson);

Now let’s put it together and finalize serializeToInfolog() method by adding exception handling and output to infolog:

[SysEntryPointAttribute]
public void serializeToInfolog()
{
    DirPersonInfoData person = this.getPersonData();
    System.Object clrPerson;
    System.Exception ex;
    str serialized;
 
    if (!xSession::isCLRSession())
    {
        throw error("Must run CIL!");
    }
 
    try
    {
        clrPerson = CLRInterop::getObjectForAnyType(person);
        serialized = Demo.ContractSerializer::Serialize(clrPerson);
    }
    catch (Exception::CLRError)
    {
        ex = CLRInterop::getLastException();
        throw error(ex.ToString());
    }
 
    info(serialized);
}

And this is how DirPersonInfoData will be serialized:

<DirPersonInfoData xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns="http://schemas.datacontract.org/2004/07/Dynamics.Ax.Application">
    <parmPersonCommunicatorSignIn />
    <parmPersonName>John Doe</parmPersonName>
    <parmPersonPrimaryEmail>john(at)doe.com</parmPersonPrimaryEmail>
    <parmPersonUserId>jdoe</parmPersonUserId>
    <parmWorkerTitle />
</DirPersonInfoData>

The fact that the same X++ code can be executed once in a native AX session and once in a CLR session is very powerful and you usually don’t have to care about how it works. Of course, it may be sometimes confusing. For example, you need to know how your code is executed in a given moment to use the right debugger.

Here I showed how to leverage it even more. The solution works because we can define a type in X++ and use it later as a CLR type. But it also depend on the fact how the CIL generator deals with DataContractAttribute, which is something you can only find by looking into CIL. A CIL decompiler comes handy if you want to play with these things.