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.

9 Comments

  1. Thanks so much for this. I have your sample working perfectly however I am trying to integrate this into an AIF Custom Service using outbound port file adapter. Here is what I want to do:
    – Custom service retrieves data from data contract that reads AX table
    – I want to use your .NET data contract serializer to serialize the data contract into XML
    – The XML is returned as the output of the service operation to be exported as an XML file

    I am trying to call the .NET class from my service operation, passing it my data contract but I keep getting AIF “object reference” errors. I’m not sure the best way to integrate your .NET serializer into my custom service so I can end up with XML I can then apply an XSLT transform to in the outbound port. Thanks in advance.

    • Robert, unfortunmately I can’t tell you where you have “object references” errors – I’ve never seen your code, much less your runtime errors. You’ll have to debug it by yourself. Think about what your code does differently than my SerializationDemo (which works, as you said).
      By the way, why do you need XSLT if you own the whole service? Why don’t you return the right data directly?

  2. Hi Martin,

    I am trying to do a Deserialize operation same way that you have done here.

    Please find below the code of C#
    ————————————————————-

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
    using System.IO;
    using System.Xml;

    namespace AXXmlDeserializer
    {
    public class XmlDeserializer
    {
    public static object Deserialize(Object dataContract, string xmlFilePath)
    {
    Type type = dataContract.GetType();

    XmlSerializer deserializer = new XmlSerializer(type);
    TextReader reader = new StreamReader(xmlFilePath);
    dataContract = deserializer.Deserialize(reader);
    reader.Close();

    return dataContract;
    }
    }
    }

    and code of AX class.
    where Fox_AckDescHandleDataContract is a Data contract class.

    ——————————————–

    [SysEntryPointAttribute]
    public void serializeToInfolog()
    {
    Fox_AckDescHandleDataContract ack = new Fox_AckDescHandleDataContract();

    // DirPersonInfoData person = this.getPersonData();
    System.Object clrPerson;
    System.Exception ex;
    //str serialized;

    if (!xSession::isCLRSession())
    {
    throw error(“Must run CIL!”);
    }

    try
    {
    clrPerson = CLRInterop::getObjectForAnyType(ack);
    ack = AXXmlDeserializer.XmlDeserializer::Deserialize(clrPerson,”C:\\FoxCustomXmlLatest_schemas_datacontract_org_2004_07_Dynamics_Ax_Application_output.xml”);
    }
    catch (Exception::CLRError)
    {
    ex = CLRInterop::getLastException();
    throw error(ex.ToString());
    }

    info(ack.ResponseDesc());

    }

    I am getting the below error :

    System.InvalidCastException: Unable to cast object of type ‘Dynamics.Ax.Application.Fox_AckDescHandleDataContract’ to type ‘System.Type’. at Dynamics.Ax.Application.SerializationDemo.Serializetoinfolog() in SerializationDemo.serializeToInfolog.xpp:line 18 — End of inner exception stack trace

    Any clue of this error ?

    Regards,
    Nikhil

    • sorry, i try to show a serialization with a collection of typs…

      <PurchaseOrder
      <customerName…</customerName
      <items
      <Item…</Item
      <Item…</Item
      <Item…</Item

      </items
      <comments
      <string…</string
      <string…</string
      <string…</string

      </comments
      </PurchaseOrder

Leave a Reply

Your email address will not be published.