Sunday, May 10, 2015

AX2012 AIF Document Services - Include fields which are not part of AOT query

While exporting data from AX to other third party systems. There could be a requirement to send data which is not part of your AOT query. There could be a requirement to include data from other tables which are not part of your query OR cannot become part of query because of complex joins or may be they need some custom business logic which cannot be achieved using Query. You can always include such fields by creating parm methods for these fields in AX<tableName> classes. For example:

public str parmWBS(str _wbs = '')
{
    GeneralJournalEntry     generalJournalEntry;
    ProjInvoiceJour         projInvoiceJour;

    if (this.parmText() == '300')
    {
        select generalJournalEntry
            where generalJournalEntry.RecId == GeneralJournalAccountEntry.GeneralJournalEntry
            && generalJournalEntry.JournalCategory == LedgerTransType::Project
        join projInvoiceJour
            where projInvoiceJour.LedgerVoucher == generalJournalEntry.SubledgerVoucher;

        if (projInvoiceJour)
        {
            _wbs = projInvoiceJour.ProjInvoiceProjId;
        }
    }

    return _wbs;
}

In above method, based on the existing parmText() field on Query, I am trying to fetch the Project Invoice Id from the table which is not part of the query.

Once, parm method is written, you will need to:


  1. Run the Update Document Service Wizar
  2. Open AIFService forms, filter the service you are working on and click Refresh
  3. Generate Incremental IL

AX 2012 upload file to FTP with batch processing support

One of a very common requirements for any ERP systems is to have FTP file upload support to upload files from AX to FTP server. We do have many libraries, tools available to do that. However, it is best to have native X++ code without referencing any DLLs to upload your files to FTP from AX. Following code shows the example of uploading files to FTP.

The code written below can be easily used to upload files, However, if you have to process this as a batch you need to consider that batch runs on server/CIL and for this you need to set permissions and also if that still doesn't work, you can change the method to static server.

private server static void sendToFTP()
{
    System.Object ftpo;
    System.Object ftpResponse;
    System.Net.FtpWebRequest request;
    System.IO.StreamReader reader;
    System.IO.Stream requestStream;
    System.Byte[] bytes;
    
    System.Text.Encoding utf8;
    System.Net.FtpWebResponse response;
    System.Object credential;
    HOSInventParameters hosInventParameters;

    int             fileCount;
    System.Array    files;
    int             i;
    str             nextFile;
    Filename        filepath;
    Filename        fileType,fileNameString;
    FileNameType    pattern = '*.TXT';
    FileName        ftpFileName,fileName;


    select firstOnly custInventParameters;
    new InteropPermission(InteropKind::ClrInterop).assert();



    files           = System.IO.Directory::GetFiles(custInventParameters.PartsFileLocation, pattern);

    if (files)
    {
        fileCount =    files.get_Length();

        for(i=0; i < fileCount; i++)
        {
            nextFile    = files.GetValue(i);

            [filepath, filename, fileType] = fileNameSplit(nextFile);
            fileNameString= filename + fileType;
            ftpFileName = custInventParameters.PartsFTPAddress + '/' + custInventParameters.PartsFTPLocation + '/' + fileNameString;

            reader = new System.IO.StreamReader(nextFile);
            utf8 = System.Text.Encoding::get_UTF8();
            bytes = utf8.GetBytes( reader.ReadToEnd() );
            reader.Close();
            // little workaround to get around the casting in .NET
            ftpo = System.Net.WebRequest::Create(ftpFileName);
            request = ftpo;

            credential = new System.Net.NetworkCredential(custInventParameters.PartsUserId,custInventParameters.PartsFTPPassword);
            request.set_Credentials(credential);
            request.set_ContentLength(bytes.get_Length());
            request.set_Method('STOR');
            // "Bypass" a HTTP Proxy (FTP transfer through a proxy causes an exception)
            // request.set_Proxy( System.Net.GlobalProxySelection::GetEmptyWebProxy() );
            requestStream = request.GetRequestStream();
            requestStream.Write(bytes,0,bytes.get_Length());
            requestStream.Close();

            ftpResponse = request.GetResponse();
            response = ftpResponse;
            info(response.get_StatusDescription());
        }
    }

    CodeAccessPermission::revertAssert();
}


Monday, April 6, 2015

Calling RESTful services using AX 2009, AX 2012


While integrating Dynamics AX with other systems, you might need to call REST services in Dynamics AX to Pull data in AX or Push data out of AX to other systems. REST services are the easiest and very useful way to provide the interface between 2 systems. The Job written below is the example of how one can call REST service from within AX.

Tip: Either you want to push data from AX or pull data in AX, you can always use POST method. In case you want to send data out of AX, you will always need to use POST method with your data as shown below in postData variable. In case, you need to download data in AX from some other system, you can still use POST method and send your credentials, API key for authentication purpose and download your data either in XML or JSON format as I have done in returnValue variable in job written below.

static void Job1(Args _args)
{
    str                             url;
    str                             postData;
    str                             returnValue;
    System.Net.HttpWebRequest       request;
    System.Net.HttpWebResponse      response;
    System.Byte[]                   byteArray;
    System.IO.Stream                dataStream;
    System.IO.StreamReader          streamReader;
    System.Net.ServicePoint         servicePoint;
    System.Net.ServicePointManager  servicePointManager;
    CLRObject                       clrObj;
    System.Text.Encoding            utf8;

    ;

    postData = strfmt('api_key=%1&account_id=%2', "DEC8A896-9244-4FAB-ACB9-15B932E630CB", "3");

    new InteropPermission(InteropKind::ClrInterop).assert();

    url = "http://yoururl.com/services/api/external/authentication.ashx?login";

    clrObj = System.Net.WebRequest::Create(url);
    System.Net.ServicePointManager::set_Expect100Continue(false);

    request = clrObj;
    request.set_Method("POST");
    utf8 = System.Text.Encoding::get_UTF8();
    byteArray = utf8.GetBytes(postData);
    request.set_ContentType("application/x-www-form-urlencoded");
    request.set_ContentLength(byteArray.get_Length());

    dataStream = request.GetRequestStream();
    dataStream.Write(byteArray, 0, byteArray.get_Length());
    dataStream.Close();

    try
    {
        response = request.GetResponse();
    }
    catch (Exception::CLRError)
    {
    postdata = "";
    }

    dataStream = response.GetResponseStream();
    streamReader = new System.IO.StreamReader(dataStream);
    returnValue = streamReader.ReadToEnd();

    info(returnvalue);

    streamReader.Close();
    dataStream.Close();
    response.Close();


}

Thursday, February 12, 2015

Easiest say to obtain SID - AX 2012 installation

We often need to obtain SID while installing or moving Dynamics AX across different environments. There are several ways to obtain SID which we need to set in the database table. However, the easiest way to obtain SID is to:

Open command prompt and type the following:

whoami /user

That's it.

Wednesday, December 31, 2014

Applying Current User Id range in an AOT query

If you want to apply a range to an AX 2012 AOT query, you can simply add a new range to the field and specify the integer or string value against them. However, if you want to apply a range on a User Id field and want to provide the value of Currently logged in user, you need to get the value dynamically and pass that value to the query. Below snap shot shows a very simple way to achieve this.


Tips on Extensible Data Security in AX 2012

Recently, I had chance to work on Extensible Data Security and noticed few things which can be very helpful, they are posted below.

  1. XDS can hurt performance sometimes if query is not designed well. In order to design security without hurting the performance, always try to reduce number of joins in your Security Policy Query.
  2. If your requirement is complex and needs to add some dynamic filters to the query. In that case you can create a temporary table with type TEMPDB and override its xds() method to fill the table dynamically. This method can be considered for performance improvements as it would reduce query joins. For this, please see xds method on the table MyLegalEntitiesForXDS
  3. If you have to disable XDS, consider following 2 methods
    1. Create a view and fetch data from that view. View is a different object for XDS framework, thus the security applied on the table will not be considered for view and you will see the data.
    2. Second method to disable the security is mentioned here in my other post.

AX 2012 Disable/ByPass extensible data security (XDS) through code

Extensible Data Security or XDS is a framework introduced in AX 2012 to apply security on data. We had RLS or Record Level Security in AX 2009. However, XDS is completely a new framework with more capabilities in order to apply data security. This post is about disabling or bypassing the XDS using code. For more details about XDS, download a whitepaper provided by Microsoft using following link.
http://www.microsoft.com/en-us/download/details.aspx?id=3110

Disabling/Bypassing XDS or Extensible Data Security:
Recently, I had a requirement to restrict users from viewing all the Warehouses available and they must be able to see only the Warehouses they belong to. I designed a new security policy and it started working very easily without much problem. However, the problem was on Inventory Transfer From where we have two fields for Warehouses:
  1. From warehouse and
  2. To warehouse
Our requirement was to restrict only From warehouse and To warehouse MUST show all the warehouses available so they can issue inventory to any warehouse but should not be allowed to issue inventory from the warehouse they don't belong to. In order to achieve this, I had to disable the XDS on To Warehouse lookup method. I did override the lookup() method on To Warehouse field on the datasource and below is the code which I wrote to disable the XDS.

public void lookup(FormControl _formControl, str _filterStr)
{
    XDSServices                      xXDS = new XDSServices();

    xXDS.setXDSState(0); // Disable XDS
    super(_formControl, _filterStr);
    xXDS.setXDSState(1); // Enable XDS
}