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
Nice post Alex :-)
ReplyDeletespectacular post Alex. i will *borrow" this :)
ReplyDeleteHi Alex,
ReplyDeleteCan 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
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.
ReplyDeleteplease keep up the good work!!
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.
ReplyDeleteI would want to know the best way doing of this kind of work.