Sunday, November 9, 2014

Microsoft Dynamics AX 2012 Custom Web Services Wizard

Microsoft Dynamics AX 2012 Custom Web Services Wizard
 
Purpose: The purpose of this document is to illustrate how to generate Data Contract and Service Contract classes for Custom Web Services in Microsoft Dynamics AX 2012.
 
Challenge: In order to use Custom Web Services in Microsoft Dynamics AX 2012 you have to introduce appropriate Data Contract and Service Contract classes. Often times Data Contract classes will be created based on existing data model (for example, tables or views) as is which makes it possible to automate this process.           
 
Solution: In order to generate Data Contract and Service Contract classes for Custom Web Services in Microsoft Dynamics AX 2012 based on existing data model as is I created a simple X++ job automating this process. 
 
Walkthrough
 
Often times I create Custom Web Services in Microsoft Dynamics AX 2012 that’s why I decided to automate some of the routine work I usually do when creating Data Contracts and Service Contracts
 
Let’s discuss Data Contracts first. When I create a new Data Contract based on existing data model as is, say, we take CustGroup table as an example I’ll have to create a class and a number of methods to expose different data elements/fields. More specifically for each data element/field I want to expose I’d create a separate method with DataMemberAttribute attribute which is used to define serialization of the accessor method as a data member
 
 
In case you have just a few fields to expose it will not take a lot of time, however if you have more than just a few fields the process may be tedious. Here is how we can generate Data Contract class for our example in a flash
 
Source code
 
static void DataContract(Args _args)
{
    ClassBuild classBuild;
 
    str        header;
    str        vars;
    str        newClassName = "AlexDataContract";
 
    DictTable  dictTable;
    DictField  dictField;
    DictType   dictType;
 
    TableId    tableId;
    FieldId    fieldId;
    ExtendedTypeId typeId;
 
    str        tableName = "CustGroup";
    str        fieldName;
    str        typeName;
 
    int        i;
    ;
 
    header = 'public class ' + newClassName;
 
    classBuild = new ClassBuild(newClassName);
   
    tableId = tableName2id(tableName);
    dictTable = new DictTable(tableId);
   
    if (dictTable)
    {   
        for (i=1; i<= dictTable.fieldCnt(); i++)
        {
            fieldId = dictTable.fieldCnt2Id(i);
            dictField = new DictField(tableId, fieldId);
           
            if (dictField)
            {
                typeId = dictField.typeId();
               
                if (typeId != 0)
                {
                    dictType = new DictType(typeId);
        
                    if (dictType)
                    {
                        if (!dictField.isSystem())
                        {
                            fieldName = strFmt("%1", dictField.name());           
                            typeName = strFmt("%1", dictType.name());
           
                            classBuild.addMethod(fieldName, '[DataMemberAttribute]' + '\n' +
                            'public ' + typeName + ' ' + fieldName + '(' + typeName + ' _' + fieldName + ' = ' + fieldName + ')' + '\n' +
                            '{' + '\n' +
                            '\t' + fieldName + ' = _' + fieldName + ';' + '\n' +
                            '\t' + 'return ' + fieldName + ';' + '\n' +
                            '}');
                           
                            vars += '\t' + typeName + "\t" + fieldName + ';' + '\n';
                        }
                    }
                }
            }
        }
    }
   
    classBuild.addMethod('classdeclaration', '[DataContractAttribute]' + '\n' + header + '\n{\n' + vars + '}\n');
   
    classBuild.classNode().AOTcompile(1);
}
 
Here’s the result
 
 
Example
 
[DataContractAttribute]
public class AlexDataContract
{
         PlTaxPeriodPaymentCode    TaxPeriodPaymentCode_PL;
         DimensionDefault DefaultDimension;
         BankCustPaymIdRecId       BankCustPaymIdTable;
         CustVendTaxGroup TaxGroupId;
         PaymTermId       PaymTermId;
         ClearingPeriod   ClearingPeriod;
         Description      Name;
         CustGroupId      CustGroup;
}
 
 
[DataMemberAttribute]
public BankCustPaymIdRecId BankCustPaymIdTable(BankCustPaymIdRecId _BankCustPaymIdTable = BankCustPaymIdTable)
{
         BankCustPaymIdTable = _BankCustPaymIdTable;
         return BankCustPaymIdTable;
}
[DataMemberAttribute]
public ClearingPeriod ClearingPeriod(ClearingPeriod _ClearingPeriod = ClearingPeriod)
{
         ClearingPeriod = _ClearingPeriod;
         return ClearingPeriod;
}
[DataMemberAttribute]
public CustGroupId CustGroup(CustGroupId _CustGroup = CustGroup)
{
         CustGroup = _CustGroup;
         return CustGroup;
}
[DataMemberAttribute]
public DimensionDefault DefaultDimension(DimensionDefault _DefaultDimension = DefaultDimension)
{
         DefaultDimension = _DefaultDimension;
         return DefaultDimension;
}
[DataMemberAttribute]
public Description Name(Description _Name = Name)
{
         Name = _Name;
         return Name;
}
[DataMemberAttribute]
public PaymTermId PaymTermId(PaymTermId _PaymTermId = PaymTermId)
{
         PaymTermId = _PaymTermId;
         return PaymTermId;
}
[DataMemberAttribute]
public CustVendTaxGroup TaxGroupId(CustVendTaxGroup _TaxGroupId = TaxGroupId)
{
         TaxGroupId = _TaxGroupId;
         return TaxGroupId;
}
[DataMemberAttribute]
public PlTaxPeriodPaymentCode TaxPeriodPaymentCode_PL(PlTaxPeriodPaymentCode _TaxPeriodPaymentCode_PL = TaxPeriodPaymentCode_PL)
{
         TaxPeriodPaymentCode_PL = _TaxPeriodPaymentCode_PL;
         return TaxPeriodPaymentCode_PL;
}
 
Please note that each accessor method return type is based on actual EDT used for the field
 
Speaking about Service Contracts what I did I generated a backbone of Service Contract class with empty action methods, however some more automation can be provided for Service Contract class too as necessary 
 
Source code
 
static void ServiceContract(Args _args)
{
    ClassBuild classBuild;
 
    str header;
    str newClassName = "AlexServiceContract";
    ;
 
    header = 'public class '+ newClassName;
 
    classBuild = new ClassBuild(newClassName);
 
    classBuild.addMethod('classdeclaration', header + '\n{\n}\n');
 
    classBuild.addMethod('createEntity', '[SysEntryPointAttribute(true)]' + '\n' + 'public void createEntity()' + '\n{\n}\n');
    classBuild.addMethod('readEntity', '[SysEntryPointAttribute(true)]' + '\n' + 'public void readEntity()' + '\n{\n}\n');
    classBuild.addMethod('updateEntity', '[SysEntryPointAttribute(true)]' + '\n' + 'public void updateEntity()' + '\n{\n}\n');
    classBuild.addMethod('deleteEntity', '[SysEntryPointAttribute(true)]' + '\n' + 'public void deleteEntity()' + '\n{\n}\n');
 
    classBuild.classNode().AOTcompile(1);
}
 
As the result I will have Service Contract class generated with placeholder methods which can be then filled with X++ code
 
 
Example
 
public class AlexServiceContract
{
}
[SysEntryPointAttribute(true)]
public void createEntity()
{
}
[SysEntryPointAttribute(true)]
public void deleteEntity()
{
}
[SysEntryPointAttribute(true)]
public void readEntity()
{
}
[SysEntryPointAttribute(true)]
public void updateEntity()
{
}
 
In case you just need to expose some read-only data this approach can also be applied for views. Using views you can create a representation of an entity which consists of multiple tables 
 
For the sake of simplicity in this walkthrough I’ll just wrap CustGroup table with a view and generate Data Contract and Service Contract classes for it, in fact your view may consist of multiple tables as necessary
 
 
Source code
 
static void DataContract(Args _args)
{
    ClassBuild classBuild;
 
    str        header;
    str        vars;
    str        newClassName = "AlexDataContract";
 
    DictTable  dictTable;
    DictField  dictField;
    DictType   dictType;
 
    TableId    tableId;
    FieldId    fieldId;
    ExtendedTypeId typeId;
 
    str        tableName = "AlexCustGroup";
    str        fieldName;
    str        typeName;
 
    int        i;
    ;
 
The result will be identical
 
 
Now we could also implement readEntity method on Service Contract which returns entity details  
 
Please note that staging tables can also be used to represent entity which consists of multiple tables much like this is done in Microsoft Dynamics AX 2012 DIXF (Data Import Export Framework). Or you might want to generate distinct Data Contracts for distinct tables, say, header and lines example
 
Please find more info about how to create Custom Web Services in Microsoft Dynamics AX 2012 here: http://technet.microsoft.com/en-us/library/hh509052.aspx
 
For more practical examples of using Custom Web Services in Microsoft Dynamics AX 2012 you may also visit my another blog here: http://ax2012aifintegration.blogspot.com/2013/11/microsoft-dynamics-ax-2012-wcf-custom.html
 
Summary: This document describes how to generate Data Contract and Service Contract classes for Custom Web Services in Microsoft Dynamics AX 2012.
 
Tags: Microsoft Dynamics AX 2012, Custom Web Services, Data Contract, Service Contract, X++.
 
Note: This document is intended for information purposes only, presented as it is with no warranties from the author. This document may be updated with more content to better outline the issues and describe the solutions.
 
Author: Alex Anikiev, PhD, MCP 

5 comments:

  1. spectacular post Alex. i will *borrow" this :)

    ReplyDelete
  2. Hi Alex,

    Can you please help me to know how can we make a Data member attribute as Mandatory in the schema exposed by Custom Service ?

    I know this is possible implementing the SysOperationValidatable but I think this will not affect the WCF schema.

    Regards,
    Nikhil

    ReplyDelete
  3. thx Alex! I was about to build something like this today when I remembered your post popping up on my reader a few months ago. Just tested it and it works great.
    please keep up the good work!!

    ReplyDelete
  4. Hi Alex, I would like to know about the best practices for returning custom error message and code from an AIF service. We are doing some business case validations within our AIF services and returning the error code+message in form of string from the service.
    I would want to know the best way doing of this kind of work.

    ReplyDelete