Rahul Sharma

Solutions Architect - Microsoft Dynamics AX | Azure

Blog
This is a technology blog covering Microsoft Dynamics AX, Microsoft Dynamics CRM, Microsoft Azure, IoT, .Net, SharePoint, SQL Server, SSRS, SSAS, SSIS, Apache Cassandra, MongoDB, and related technologies. Join this blog on facebook {Rahul Sharma}, to start a discussion !!! NOTE: My employer is not responsible for the content published here.

Index | MS Dynamics AX | MS Dynamics CRM
View blog as >> Magazine | Sidebar | Flipcard | Mosaic | Snapshot | Timeslide

Consuming ASMX WebService or WCF Service in Dynamics Ax

This post is a quick reference on consuming asmx / svc services in Dynamics Ax. We will also see how to pass .Net types to / from X++ code.

There are two methods available in .Net to create web services, one is old ASMX style and another is WCF service.
(How to create web service? Do google / bing.)

I have created a ASMX web service and deployed it on my local Windows 7 64-bit IIS 7 laptop, http://rahul:200/TestWebService/HelloWorldWS.asmx. This web service contains two web methods. First method takes an integer argument and returns a string value. Second method takes an array as input and returns an array.

ASMX C# code:
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:  using System.Web.Services;
   6:   
   7:  [WebService(Namespace = "http://tempuri.org/")]
   8:  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   9:  // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
  10:  // [System.Web.Script.Services.ScriptService]
  11:  public class HelloWorldWS : System.Web.Services.WebService
  12:  {
  13:      public HelloWorldWS () {
  14:          //Uncomment the following line if using designed components 
  15:          //InitializeComponent(); 
  16:      }
  17:   
  18:      [WebMethod]
  19:      public string GetData(int value)
  20:      {
  21:          return string.Format("You entered: {0}", value);
  22:      }
  23:   
  24:      [WebMethod]
  25:      public string[] GetDataArray(string[] value)
  26:      {
  27:          string[] ret = value;
  28:   
  29:          return ret;
  30:      }
  31:      
  32:  }

With same functionality WCF service code will look like this and it is also deployed on my laptop at http://rahul:201/Service.svc.
WCF C# code:
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Runtime.Serialization;
   5:  using System.ServiceModel;
   6:  using System.ServiceModel.Web;
   7:  using System.Text;
   8:   
   9:  // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service" in code, svc and config file together.
  10:  public class Service : IService
  11:  {
  12:      public string GetData(int value)
  13:      {
  14:          return string.Format("You entered: {0}", value);
  15:      }
  16:   
  17:      public string[] GetDataArray(string[] value)
  18:      {
  19:          string[] ret = value;
  20:   
  21:          return ret;
  22:      }
  23:  }

Add reference for above web services in Dynamics Ax.
You need to go to AOT --> References --> Add service reference. Here you need to add WSDL URL for your web services. You get WSDL URL by adding "?wsdl" at the end of your web service URL.
ASMX WSDL is: http://rahul:200/TestWebService/HelloWorldWS.asmx?wsdl
WCF WSDL is: http://rahul:201/Service.svc?wsdl

AX will generate a proxy .net assembly (namespace provided in above figure) and configuration file (app.config) for the web service. You can find this in your AX installation path at
<Microsoft Ax Dynamics>\50\Application\Appl\DynamicsAx50\ServiceReferences

Create a wrapper AX class for the proxy.
Now we will create a wrapper class for the generated web service proxy. By design, AX needs this class to be running on server tier. So, set class RunOn property to server and create two methods GetData(int _input) & GetDataArray(Container _inputContainer). You can also create a main() to test web service call at server tier. Then we will create a job that will run on client tier to call our wrapper class.
X++ code:
   1:  //X++ (ASMX web service wrapper) class running on server (RunOn: Server)
   2:  public class HelloWorldWS
   3:  {
   4:  }
   5:   
   6:  //this methods passes int value to web service that return System.String
   7:  public static server str GetData(int _input = 0)
   8:  {
   9:      Rah.HelloWorldWS.HelloWorldWSSoapClient ws;//web service proxy
  10:      System.String                           wsResult;//web method's return value
  11:      str                                     retStr;
  12:      ;
  13:   
  14:      try
  15:      {
  16:          new InteropPermission(InteropKind::ClrInterop).assert();
  17:          
  18:          //call web service
  19:          ws = new Rah.HelloWorldWS.HelloWorldWSSoapClient("HelloWorldWSSoap");
  20:          wsResult = ws.GetData(_input);
  21:   
  22:          //check if web service call returned value.
  23:          //web service can return a null object and this null object is not supported in x++
  24:          if (!CLRInterop::isNull(wsResult))
  25:          {
  26:             info("wrapper: " + any2str(CLRInterop::getAnyTypeForObject(wsResult)));
  27:             retStr = any2str(CLRInterop::getAnyTypeForObject(wsResult));
  28:          }
  29:          CodeAccessPermission::revertAssert();
  30:          return retStr;
  31:      }
  32:      catch (Exception::CLRError)
  33:      {
  34:          throw error(AifUtil::getClrErrorMessage());
  35:      }
  36:  }
  37:   
  38:  //this method passes System.String[] as input parameter to web service and
  39:  //receives System.String[] as output
  40:  public static server container GetDataArray(container _inputContainer = connull())
  41:  {
  42:      Rah.HelloWorldWS.HelloWorldWSSoapClient ws;//web service proxy
  43:      System.String[]                         wsInputArrS;//web method's input argument
  44:      System.Array                            wsResultArr;//web method's return value
  45:      int                                     wsResultArrLen;//return value's length
  46:   
  47:      int                                     i;
  48:      container                               retCon;//array converted to x++ container, to pass it on client tier
  49:      int                                     inputConLen;
  50:      ;
  51:   
  52:      if (_inputContainer != connull())
  53:      {
  54:          inputConLen = conlen(_inputContainer);
  55:   
  56:      }
  57:      else
  58:          return retCon;
  59:   
  60:      try
  61:      {
  62:          new InteropPermission(InteropKind::ClrInterop).assert();
  63:   
  64:          //initialize .NET Framework array by special X++ syntax, note () at the end
  65:          wsInputArrS = new System.String[inputConLen]();
  66:   
  67:          //prepare input
  68:          for (i = 0; i < inputConLen; i++)
  69:          {
  70:              wsInputArrS.SetValue(CLRInterop::getObjectForAnyType(conpeek(_inputContainer, i+1)), i);
  71:          }
  72:   
  73:          //call web service
  74:          ws = new Rah.HelloWorldWS.HelloWorldWSSoapClient("HelloWorldWSSoap");
  75:          wsResultArr = ws.GetDataArray(wsInputArrS);
  76:   
  77:          //check if web service call returned value.
  78:          //web service can return a null object and this null object is not supported in x++
  79:          if (!CLRInterop::isNull(wsResultArr))
  80:          {
  81:              wsResultArrLen = wsResultArr.get_Length();
  82:              for (i = 0; i < wsResultArrLen; i++)
  83:              {
  84:                  info("wrapper: " + any2str(CLRInterop::getAnyTypeForObject(wsResultArr.GetValue(i))));
  85:                  retCon += any2str(CLRInterop::getAnyTypeForObject(wsResultArr.GetValue(i)));
  86:              }
  87:          }
  88:          CodeAccessPermission::revertAssert();
  89:          return retCon;
  90:      }
  91:      catch (Exception::CLRError)
  92:      {
  93:          throw error(AifUtil::getClrErrorMessage());
  94:      }
  95:  }
  96:   
  97:  //test web service call on server tier
  98:  public static void main(Args _args)
  99:  {
 100:      str             ret;
 101:      container       retCon, con;
 102:      int             i;
 103:      ;
 104:   
 105:      ret = HelloWorldWS::GetData(123);
 106:   
 107:      con += "hello-";
 108:      con += "world";
 109:      retCon = HelloWorldWS::GetDataArray(con);
 110:   
 111:      //use web service results
 112:      info("main: " + ret);
 113:      ret = '';
 114:   
 115:      for (i =  0; i < conlen(retCon); i++)
 116:      {
 117:          ret += conpeek(retCon, i+1);
 118:      }
 119:   
 120:      info("main arr: " + ret);
 121:  }

Also try to call this from AX job.
   1:  public static void HelloWorldWS_Arr(Args _args)
   2:  {
   3:      str             ret;
   4:      container       retCon, con;
   5:      int             i;
   6:      ;
   7:   
   8:      ret = HelloWorldWS::GetData(123);
   9:   
  10:      con += "hello-";
  11:      con += "world";
  12:      retCon = HelloWorldWS::GetDataArray(con);
  13:   
  14:      //use web service results
  15:      info("job: " + ret);
  16:      ret = '';
  17:   
  18:      for (i =  0; i < conlen(retCon); i++)
  19:      {
  20:          ret += conpeek(retCon, i+1);
  21:      }
  22:   
  23:      info("job arr: " + ret);
  24:  }

Same logic can be applied to create wrapper class for WCF service.

Update:
You also need to copy the generated app.config and proxy .net assembly from ServiceReferences directory to Server\bin directory. If you don't copy app.config you will get CLRObject could not be created errors when trying to instantiate the first non-service client class. And if the assembly itself is not copied you will get missing reference errors thrown at runtime when instantiating the arrays. 

Please join this blog if you liked this post.
Also feel free to post your comment / feedback / queries.
Comments
1 Comments