Building Your Own Export Framework with Dynamics AX (part 2)


I’ve been enjoying a relaxed sojourn in the beautiful city of Venezia, Italia with my Fiancée.

After two months, I’m finally back in the blog-posting saddle!  Today, I want to pick up on this blog series we began in early January.  If you’re just picking up on this now, the previous post details our motives for building an entirely new and light-weight export functionality – avoiding flat file integration, we can support direct-to-API eventing because AX 2012 is just that powerful.

With our simple WebAPI in place from Part 1, let’s build a Visual Studio solution direct within AX.  In this example, I’m using AX 2012 R3 CU8 (so you’ll see screenshots with VS 2013) however this is generally applicable to all versions of AX 2012. 

To start, let’s create a new Class Library and call it whatever you like.  I’ll call mine AXExportFramework.

Next, let’s rename Class1.cs to something more useful, like DataExportUtil.  Then, add a few helpful namespaces such as System.Dynamic, XML, .NET, and XML.Linq:

Create a public class variable that is of a Dynamic type.  Then, instantiate it in a public constructor, as a new ExpandoObject():

Dynamic objects are loosely typed, meaning we as developers do not know at compile-time what properties this object will have.  I’ve named ExportEntity generically on purpose, as it can reference Customers, or any other Export Entity we setup in the future.  This same object can have ExportEntity.CustId in one instance, and ExportEntity.ProductNumber in another instance.  It gives you total flexibility.

ExpandoObjects are essentially a dynamic dictionary.  We can add properties at any time, and then later retrieve their values.  We can use syntax such as [Entity].[Property].  We later can map these entity properties from our API in AX.  More on that in the final post!

For now, let’s finish out the class library.  Create two helper methods to add properties to the dynamic object, and retrieve their values:

Finally, we add an Export() method that will pass data from AX off to our API.  This method will be generic in that we can pass it any object. For now, we just have Customers setup in the API.

Of course, you could create a separate export class to handle each and every entity. Instead of the traditional strongly-typed model where we have a known Object and can serialize and deserialize, we will stick strictly with XML.  This is how our ExpandoObject will help us.

Call the "get" API operation (/api/Customers/{id of zero}), and retrieve back an empty Customer data contract.  From there, we turn it in to an XML document, and iterate the nodes.  If a node is a property that we have specified on our Dynamic Object, we update the XML node value with the Dynamic property value.  We then can save our XML document back to a string and upload it back to the API for saving to an external database. Magic!

Check it out for yourself below:

   1:              Boolean ret = true;
   2:              string objectType = ExportEntity.ObjectType; //we add ObjectType at run-time in AX
   3:              string apiCMD = _baseURI + objectType + "/0";
   4:              string dataContract = String.Empty;
   5:              WebClient cli = new WebClient();
   6:              XDocument xDoc;
   7:   
   8:              //we need to set our webClient to accept XML instead of json
   9:              cli.Headers.Add("Accept", "application/xml");
  10:   
  11:              try
  12:              {
  13:                  //we use DownloadString to capture the default XML template
  14:                  dataContract = cli.DownloadString(apiCMD);
  15:   
  16:                  // parse to an XDocument
  17:                  xDoc = XDocument.Parse(dataContract);
  18:                  
  19:   
  20:                  //traverse document and update mapped values
  21:                  foreach (XNode node in xDoc.DescendantNodes())
  22:                  {
  23:                      if (node.NodeType == XmlNodeType.Element)
  24:                      {
  25:                          XElement element = node as XElement;
  26:                          if (element.Parent == null)
  27:                          {
  28:                              //outer most XML node, we can skip
  29:                              continue;
  30:                          }
  31:                          if (element.Parent != null && element.Parent.Name != xDoc.Root.Name)
  32:                          {
  33:                              continue; //we do not map sub-nodes to entities
  34:                          }
  35:   
  36:                          dynamic objectProperty = GetProperty(element.Name.LocalName);
  37:   
  38:                          if (!String.IsNullOrEmpty(objectProperty))
  39:                          {
  40:                              string propertyString = objectProperty as String;
  41:   
  42:                              switch (propertyString)
  43:                              {
  44:                                  //convert X++ NoYes to true/false
  45:                                  case "No":
  46:                                      element.Value = "false";
  47:                                      break;
  48:                                  case "Yes":
  49:                                      element.Value = "true";
  50:                                      break;
  51:                                  default:
  52:                                      element.Value = propertyString;
  53:                                      break;
  54:                              }
  55:   
  56:                              foreach(var attrib in element.Attributes())
  57:                              {
  58:                                  if(attrib.Name.LocalName == "nil")
  59:                                  {
  60:                                      attrib.Value = "false";
  61:                                  }
  62:                              }
  63:   
  64:                          }
  65:                      }
  66:                  }
  67:   
  68:                  // "serialize" back to a string
  69:                  dataContract = xDoc.ToString(SaveOptions.DisableFormatting);
  70:   
  71:   
  72:                  //send back to API
  73:                  //we use a PUT command to update
  74:                  cli = new WebClient();
  75:                  cli.Headers.Add("Content-Type", "application/xml");
  76:   
  77:                  cli.UploadString(apiCMD, "PUT", dataContract);
  78:              }
  79:              catch (System.Exception ex)
  80:              {
  81:                  string message = ex.Message;
  82:                  if(ex.InnerException != null)
  83:                  {
  84:                      message += ex.InnerException.Message;
  85:                  }
  86:   
  87:                  Global.error(message);
  88:              }
  89:   
  90:              return ret;
  91:          }


Let’s add the Visual Studio project to the AOT by right-clicking the solution and selecting Add to AOT:

Update the class library properties to enable automatic deployment to both the Client and the Server:

Build, and Deploy your solution.  Close any open AX clients that you may have, and re-open.  Let’s create a quick Job to verify the classes are exposed properly:

Now that we know it works, you can probably guess where we’ll head in our next post in the series!  Why not export all customer data?  And, to boot, keep the exported data in sync with AX.