Tuesday, August 27, 2019

Proxy Classes for .NET Interop to X++: session open inside using statement

When you start using Proxy Classes for .NET Interop to X++ you need to open and close a session and in order to adhere to the DRY principle (do not repeat yourself) I created a new c# class to use in my project.

if you look in the examples of how to use Linq to Ax (https://docs.microsoft.com/en-us/dynamicsax-2012/developer/code-example-linq-to-ax-from-csharp) you can see in the code they have to open and close the session at the end:

using       System;  // C#
using       System.Linq;
using U23 = Microsoft.Dynamics.AX.ManagedInterop;
using U22 = Microsoft.Dynamics.AX.Framework.Linq.Data;
using       Microsoft.Dynamics.AX.Framework.Linq.Data; // .ForUpdate() needs this.

namespace LinqProviderSample
{
   class Program  // C#, LINQ to AX.
   {
      static void Main(string[] args)
      {
         // Logon to Dynamics AX.
         U23.Session axSession = new U23.Session();
         axSession.Logon(null, null, null, null);

    //more code...

    axSession.Logoff();
      }
   }
}

but what I want is to use a using statement like this so that I do not have to worry about opening and closing the session and repeat every time the same code:

public string GetTableLabel()
{
    using (AxSessionManager axSession = new AxSessionManager())
    {
        axSession.OpenConnection();

        MyTable record = new MyTable();

        var x = new SysDictTable(record.TableId);

        return x.label();
    }
}

Here the class you need to add to the project:

using System;
using System.Net;
using U23 = Microsoft.Dynamics.AX.ManagedInterop;

namespace MydDynamicsIntegration.DynamicsCommon
{
    public class AxSessionManager : IDisposable
    {
        private readonly U23.Session _axSession = new U23.Session();

        public U23.Session Connection
        {
            get { return _axSession; }
        }

        /// <summary>
        ///     Checks to see if the AX session is connected. If it's null we return false.
        ///     Then we check to see if it's logged in, then return true. Otherwise it's not logged in.
        /// </summary>
        public bool Connected
        {
            get { return _axSession != null && _axSession.isLoggedOn(); }
        }

        public void Dispose()
        {
            CloseConnection();
        }

        /// <summary>
        ///     This connects to the AX session. If it's already connected then we don't need to connect
        ///     again, so we return true. Otherwise we'll try to initiate the session.
        /// </summary>
        /// <returns>
        ///     True: Connection openned successfully, or was already open.
        ///     False: Connection failed.
        /// </returns>
        public bool OpenConnection()
        {
            if (Connected)
            {
                return true;
            }

            try
            {
                _axSession.Logon("fch", "en-GB", null, null);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool OpenConnectionAs(NetworkCredential nc, string domain)
        {
            //System.Net.NetworkCredential nc = new System.Net.NetworkCredential("ProxyUserID", "password");
            var strUserName = nc.UserName;

            if (Connected)
            {
                return true;
            }

            try
            {
                //_axSession.LogonAs(strUserName.Trim(), "domain.com", nc, "fch", "en-GB", null, null);
                _axSession.LogonAs(strUserName.Trim(), domain, nc, "fch", "en-GB", null, null);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        ///     If the session is logged on we will try to close it.
        /// </summary>
        /// <returns>
        ///     True: Connection closed successfully
        ///     False: Problem closing the connection
        /// </returns>
        public bool CloseConnection()
        {
            bool retVal;
            if (Connection.isLoggedOn())
            {
                try
                {
                    _axSession.Logoff();
                    retVal = true;
                }
                catch
                {
                    retVal = false;
                }
            }
            else
            {
                retVal = true;
            }

            Connection.Dispose();
            return retVal;
        }
    }
}


as you see from the code you can either open the connection using
axSession.OpenConnection();
or use OpenConnectionAs where you need to pass the network credentials of the logged in user that match the user in Dynamics to impersonate that user in the session.

validate UK postcode using a free WebService in x++ Dynamics Ax 2012

in Uk there is this free service http://postcodes.io/ which can be used to validate a uk postcode.

here an example on how to use it in x++ using a Web Service and parse the returned Json

static void JobEF_WebServiceTestInDialog(Args _args)
{
    Dialog dialog;
    DialogField zipCodeField;
    LogisticsAddressZipCodeId postcode;
    str result = '"result":';
    str postcodeServiceUri = 'http://api.postcodes.io/postcodes/%1/validate';
    str values, ret;
    int ptr, len;
    System.Net.WebClient webclient;
    str uri;
    NoYesId validPostCode;
    
    webclient = new System.Net.WebClient();
    
    dialog = new Dialog('Validate UK postcode using Web Service');
    
    zipCodeField = dialog.addField(ExtendedTypeStr(LogisticsAddressZipCodeId));
    
    if (dialog.run())
    {
        postcode = zipCodeField.value();
        if (!postcode)
        {
            warning('please select a post code');
            return;
        }
        
        uri = strfmt(postcodeServiceUri, postcode);
        
        // Get the value from the server given the URL.
        values = webclient.DownloadString(uri);

        // Find the result value from the JSON
        len = strLen(values);
        ptr = strScan(values, result, 1, len);
        ret = subStr(values, ptr + strLen(result), len - ptr - strLen(result));
        validPostCode = ret == 'true';
        
        info(strFmt('"%1" is %2', postcode, validPostCode ? 'a valid postcode' : 'an invalid postcode'));
    }
}

Serialize to json using x++ in Dynamics Ax 2012

here an example how to create from x++ a simple json object.

static void JobEF_ListToJson(Args _args)
{
    int i;
    str myJson;
    System.Web.Script.Serialization.JavaScriptSerializer ser = new System.Web.Script.Serialization.JavaScriptSerializer();
    System.Object oVar;
    System.Type listType = System.Type::GetType('System.Collections.ArrayList');
    CLRObject arrayList = System.Activator::CreateInstance(listType);

    new InteropPermission(InteropKind::ClrInterop).assert();
    for( i = 0; i <= 5 ; i++)
    {
        oVar = strFmt('%1 is the value %2', i, DateTimeUtil::utcNow());
        arrayList.Add(oVar);
    }

    myJson = ser.Serialize(arrayList);

    info(myJson);
}

Create a unique random number in Dynamics Ax x++

static void JobEF_RandomNumber(Args _args)
{
    System.Random rand = new System.Random();
    MYTable MYTable;
    int value = rand.Next();
    boolean found;

    info(strFmt('%1', value));
    
    while (!found)
    {
        value = rand.Next(10);
        if ((select firstOnly MYTable where MYTable.ChangeRequestId == value).RecId == 0)
            found = true;
    }
    

    info(strFmt('%1', value));
}

helper class to clear the value of a table field

static boolean clearValueOfField(
    Common _record,
    FieldID _fieldID)
{
    DictField dictField;
    ;

    dictField = new DictField(_record.TableId, _fieldID);
    if (!dictField)
        return false;

    switch (dictField.baseType())
    {
        case Types::String :
        case Types::VarString :
            _record.(_fieldID) = "";
            return true;

        case Types::Date :
            _record.(_fieldID) = datenull();
            return true;

        case Types::Real :
        case Types::Integer :
        case Types::Enum :
            _record.(_fieldID) = 0;
            return true;

        default :
            return false;
    }

    return false;
}

Effective date Form datasource filter Queryrange using check boxes to select expired active future records in Dynamics Ax

I had a requirement to create create a form where the date effective selection appears as check boxes.

image

I found a similar implementation in LogisticsPostalAddress, so here the code I will show you below based is on what you can see in \Forms\LogisticsPostalAddress\Data Sources\LogisticsLocation\Methods\executeQuery

1) override the click event of each checkbox like this:

public void clicked()
{
    super();

    element.setEffectiveDateFilter();
}

2) add this method

here I added some logic to check uncheck based the checkboxes to avoid invalid selections

public void setEffectiveDateFilter()
{
    if (DisplayExpired.value() && !DisplayActive.value() && DisplayFuture.value())
        DisplayActive.value(true);
    if (!DisplayExpired.value() && !DisplayActive.value() && !DisplayFuture.value())
        DisplayActive.value(true);

    MYTable_ds.executeQuery();
}

3) override the executeQuery of the datasource

public void executeQuery()
{
    //based on the template: \Forms\LogisticsPostalAddress\Data Sources\LogisticsLocation\Methods\executeQuery
    QueryBuildRange qbrValidFrom, qbrValidTo;
    RecId curRecordRecId;
    ValidFromDate validFrom = Global::dateNull();
    ValidToDate validTo = Global::dateMax();
    boolean expired;
    boolean active;
    boolean future;
    str queryRangeStr='';

    // Get the record currently selected
    curRecordRecId = MYTable_DS.cursor().RecId;

    expired = DisplayExpired.value();
    active = DisplayActive.value();
    future = DisplayFuture.value();

    qbrValidFrom = SysQuery::findOrCreateRange(this.query().dataSourceNo(1), fieldNum(MYTable,ValidFrom));
    qbrValidTo   = SysQuery::findOrCreateRange(this.query().dataSourceNo(1), fieldNum(MYTable,ValidTo));

    if (expired && !active && !future)
    {
        queryRangeStr = '(%1.%3 <= %4)';
        MYTable_ds.query().validTimeStateDateRange(Global::dateNull(), systemDateGet());
    }
    else if (!expired && active && !future)
    {
        queryRangeStr = DateEffectivenessCheck::queryRange(true,false,false);
        MYTable_ds.query().resetValidTimeStateQueryType();
    }
    else if (!expired && !active && future)
    {
        queryRangeStr = DateEffectivenessCheck::queryRange(false,false,true);
        MYTable_ds.query().validTimeStateDateRange(systemDateGet(), dateMax());
    }
    else
    {
        queryRangeStr = DateEffectivenessCheck::queryRange(true,true,true);
        validFrom = element.calcValidFrom();
        validTo = element.calcValidTo();
        //Debug::printDebug(strFmt('validFrom %1; validTo: %2', validFrom, validTo));
        MYTable_ds.query().validTimeStateDateRange(validFrom, validTo);
    }

    MYTable_ds.validTimeStateUpdate(ValidTimeStateUpdate::Correction);

    if (queryRangeStr)
        {
            qbrValidFrom.value(
                strFmt(queryRangeStr,
                    this.query().dataSourceTable(tableNum(MYTable)).name(),
                    fieldStr(MYTable,ValidFrom),
                    fieldStr(MYTable,ValidTo),
                    DateTimeUtil::toStr(DirUtility::getCurrentDateTime())
                )
            );
        }


    super();

        //focus again on the previously selected value
    MYTable_DS.findValue(fieldname2id(MYTable.TableId, 'RecID'), int642str(curRecordRecId));
}

Tuesday, August 20, 2019

get a list of Dynamics Ax security roles in code x++

static void FCH_SecurityRole(Args _args)
{
    SecurityRole        role;
    SecurityUserRole    userRole;

    while select userRole
    {
        info(userRole.User);

        select firstOnly role where role.RecId == userRole.SecurityRole;

        info(strFmt('%1 - %2', userRole.SecurityRole, SysLabel::labelId2String(role.Name)));

        info('--------------');
    }
}