Blog Listem

3 Ekim 2020 Cumartesi

AX 2012 - Axapta ile Rest Api kullanımı

Çalıştığım AX partner AGC Yazılım'daki bir müşterimizin hizmet aldığı servis Rest Api kullanıyordu. Forumlarda AX 2012 için yazılmış değişik örnekler buldum fakat hiçbirini çalıştırmayı başaramadım. O noktada baştan başlamaya karar verdim. 

Önce bilgisayarıma Postman'ın ücretsiz versiyonunu yükledim. Postman ile müşterinin servisini kullanmayı başardım. 

Daha sonra Postman'ın C# RestSharp kütüphanesi kodu üretme özelliğiyle C# koduna sahip oldum:





Bu kodu AX ile deploy edilebilecek bir koda çevirmeden önce projeme RestSharp kütüphanesini import etmem gerekiyordu. Visual Studio 2013 Package Manager Console ile aşağıdaki komutu yazıp  import etmeye çalıştığımda

Install-Package RestSharp 





"The underlying connection was closed: An unexpected error occurred on a send." hatası aldım. Hatanın çözümünü bir forum sayfasında buldum. Aşağıdaki kodu bir .REG dosyasına yazıp çalıştırınca artık Nuget pakedi yüklenebiliyordu:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319]
"SchUseStrongCrypto"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319]
"SchUseStrongCrypto"=dword:00000001


Ancak bu sefer de son RestSharp versiyonunun Visual Studio 2013 ile çalışmadığını gördüm:





Bir başka forum sayfasından Visual Studio 2013 ile çalışan son RestSharp versiyonunu öğrendim ve import edebildim:

Install-Package RestSharp -Version 103.1.0


Artık çalışan ve AX içinde deploy edebildiğim bir kodum vardı:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RestSharp;

namespace Ingenico
{
    public class Ingenico
    {
        public string StartDate { get; set; }
        public string EndDate { get; set; }
        public string Body { get; set; }
        public string Idx { get; set; }
        public string getPaymentSummaryReport()
        {
            var client = new RestClient("https://XXXXXXXXXXXXXXX/GetPaymentSummaryReport");
            client.Timeout = -1;
            var request = new RestRequest(Method.POST);
            request.AddHeader("Operator", "XXXXXXX");
            request.AddHeader("Authorization", "Basic XXXXXXX");
            request.AddHeader("Content-Type", "application/json");
            Body = "{\r\n\"ReportId\": "+Idx+",\r\n\"SerialList\": [\r\n\"\"\r\n],\r\n\"StartDate\": \""+StartDate+
            "\",\r\n\"EndDate\": \""+EndDate+
            "\",\r\n\"ExternalField\": \"\",\r\n\"ReceiptNo\": 0,\r\n\"ZNo\": 0,\r\n\"GetSummary\": true,\r\n\"SaleFlags\": 0,\r\n\"DeviceGroupName\": \"\"\r\n}\r\n       \r\n";
            request.AddParameter("application/json",Body, ParameterType.RequestBody);
            IRestResponse response = client.Execute(request);
            return response.Content;
        }
    }
}


Kodu deploy edip AX içinde çalıştırmaya kalktığımda proje klasöründeki RestSharp.DLL ve RestSharp.XML dosyalarını da aradığını ve AX'ın bunları bin klasörüne kopyalamadığını gördüm. Bu dosyaları server ve client bin klasörlerine kopyaladım:



Artık servise erişebiliyordum:

    Ingenico.Ingenico o = new Ingenico.Ingenico();
    str a;

    try
    {
        o.set_StartDate("2020-10-01");
        o.set_EndDate("2020-10-01");
        o.set_Idx("111111111111");
        a = o.getPaymentSummaryReport();

    info(a);


Gelen Json stringi çözümleyebilmek için de yine bir blogdan faydalandım.

 

class AGCingenico
{
    Array           jsonArray;
 
}

Önce bu metod ile json string çözümleniyor:

void parseJson(str  _json)

{
   Array           jsonArray;
   container       con;

   int             jsonSubstrNo, i;

   TextBuffer      textBuffer;

   Array           returnArray = new Array(Types::Class);

   #Define.LBracket('{')

   #Define.RBracket('}')
    str st;
    int fnd;



    fnd = strScan(_json,"]",1,strLen(_json));

    st="{"+subStr(_json,fnd+2,strlen(_json) - fnd -1);
    con+=st;



   // extract the json substrings and add them to a Array

   textBuffer = new TextBuffer();

   textBuffer.setText(_json);

   while (textBuffer.nextToken(0, #LBracket+#RBracket))

   {

       if ((jsonSubstrNo mod 2) > 0)
       {
           st =#LBracket + textBuffer.token() + #RBracket;
           con += st;
       }
       jsonSubstrNo++;

   }


   for (i = 1; i <= conLen(con); i++)

   {

       returnArray.value(i, RetailCommonWebAPI::getMapFromJsonString(conPeek(con, i)));

   }

   jsonArray = returnArray;

}

Daha sonra bu metod ile tabloya atıyorum:

void jsonToTable()
{
   MapIterator                 mi;
   Map                         data;
   int                         i;
   str                         value;
   

   for (i = 1; i <= jsonArray.lastIndex(); i++)
   {
       data = jsonArray.value(i);
       mi = new MapIterator(data);
       tmpTable.clear();

       while (mi.more())
       {
           value = data.lookup(mi.key());
     
           switch (mi.key())
           {
               case "ErrCode":
                this.parmErrCode(str2int(value));
                break;
               case "ErrDesc":
                this.parmErrStr(value);
                break;
               case "Amount":
                tmpTable.Amount = str2num_RU(value);
                break;
               case "AmountCredit":
                tmpTable.AmountCredit = str2num_RU(value);
                break;

...

...

...


Bütün iş bitti derken son bir sürprizle daha karşılaştım. Client tarafında düzgün çalışan kod CIL içinde tip dönüştürmeyle ilgili bir hata veriyordu. Bunun çözümünü de yine bir forum sayfasından buldum. Martin Drab çözümü nokta atışı veriyordu. RetailCommonWebAPI sınıfında aşağıdaki düzeltmeyi yaptım ve başka hata kalmamıştı:

//AGC memre 29.9.20
//private static Map getMap(CLRObject _dict)
private static Map getMap(System.Collections.IDictionary _dict)
//http://dev.goshoom.net/en/2016/01/x-to-cil-object-must-implement-iconvertible/#comment-135186
//AGC memre
{


Sonuç olarak AX 2012 ile Rest Api kullanımı her ne kadar kolay olsa da ortamı uyarlayana kadar çeşitli pürüzler çıkıyor, neyse ki forumlar ve bloglar var. :)


4 Eylül 2020 Cuma

AX 2012 - Workflow formu açılırken hata

  Bir müşterimizde Workflow formu açılmıyordu. Üstüne üstlük verdiği hata mesajı çok da açıklayıcı değildi:





Event log incelemesi yaptığımda hatayla ilgili bir log kaydı bulamadım. İnternette tavsiye edilen çözümlerin tümünü sırayla denedim, ancak hiçbiri işe yaramadı:

-Incremental CIL

-Full CL

-XppIL klasörünü silip AOS restart

-Full compile ve Full CIL

-Client reinstall


Bir blogda WorkflowEditorHost formunda try/catch bloğunda CRL hataları için iç hata yakalaması yapılmadığından bahsediliyordu. Bloggerın yaptığı gibi koda ekleme yapmaya karar verdim ve aşağıdaki kırmızı işaretli satırları ekledim:

 

private void build()
{
    #Admin
    #AOT
     //AGC memre
    #OCCRetryCount
    System.Exception ex;
    //AGC memre



    WorkflowVersionTable        versionTable;
    WorkflowTypeName            templateName;
    str                         domainUser;
    UserInfo                    userInfo;
    NumberSeq                   num;
    SysInfoAction_MenuFunction  sysInfoAction;
    TreeNode                    treeNode;
    SysDictWorkflowType            workflowType;
    int                         classId;

    num = NumberSeq::newGetNum(SysWorkflowParameters::numRefSequenceId(), false, true);
    if (num == null)
    {
        sysInfoAction = SysInfoAction_MenuFunction::newMenuItem(menuitemDisplayStr(SystemParameters), MenuItemType::Display);
        throw error("@SYS108268", '', sysInfoAction);
    }

    versionTable = element.args().record();

    //BP Deviation Documented
    select userInfo where userInfo.Id == curUserId();
    domainUser = userInfo.NetworkDomain + '\\' + userInfo.NetworkAlias;

    if (element.args().parmEnumType() == enumNum(WorkflowConfigurationActionType) &&
        element.args().parmEnum() == WorkflowConfigurationActionType::New)
    {
        templateName = element.args().parm();
        treeNode = TreeNode::findNode(#WorkflowTypesPath + #AOTDelimiter + templateName);

        if (treeNode)
        {
            workflowType = new SysDictWorkflowType(templateName);
            classId = className2Id(workflowType.document());
            if(classId == 0)
            {
                throw error("@SYS108554" + '. ' + "@SYS113219");
            }

            try
            {
                workflowConfiguration = Microsoft.Dynamics.AX.Framework.Workflow.Model.WorkflowModel::
                                            Create(templateName, curext(), curUserId(), domainUser);
            }
            catch (Exception::CLRError)
            {
            //AGC memre
            ex = ClrInterop::getLastException();
            if (ex != null)
            {
                ex = ex.get_InnerException();
                if (ex != null)
                {
                    error(ex.ToString());
                }
                else
                    error(AifUtil::getClrErrorMessage());
            }
            else
                    error(AifUtil::getClrErrorMessage());
            //AGC memre           
               
throw error("@SYS327400");
            }
        }
        else
        {
            throw error(strFmt("@SYS106830", templateName));
        }
    }
    else
    {
        try
        {
            workflowConfiguration = Microsoft.Dynamics.AX.Framework.Workflow.Model.WorkflowModel::Create(versionTable.ConfigurationId, curext(), domainUser);
        }
        catch (Exception::CLRError)
        {
            //AGC memre
            ex = ClrInterop::getLastException();
            if (ex != null)
            {
                ex = ex.get_InnerException();
                if (ex != null)
                {
                    error(ex.ToString());
                }
                else
                    error(AifUtil::getClrErrorMessage());
            }
            else
                    error(AifUtil::getClrErrorMessage());
            //AGC memre           
           
throw error("@SYS327400");
        }
    }

}





public void run()
{
    //AGC memre
    #OCCRetryCount
    System.Exception ex;
    //AGC memre

    try
    {
        modelEditorControl.Load(workflowConfiguration, userSettings);
        workflowEditorPane.initializeActionPane();
        if (element.args().openMode() == OpenMode::View)
        {
            isReadOnly = true;
            modelEditorControl.set_IsReadOnly(true);
            modifyElementButtonGroup.caption("@SYS322934");
            modifyWorkflow.caption("@SYS322935");
            viewToolboxButton.visible(false);
        }
    }
    catch (Exception::CLRError)
    {
    //AGC memre
        ex = ClrInterop::getLastException();
        if (ex != null)
        {
            ex = ex.get_InnerException();
            if (ex != null)
            {
                error(ex.ToString());
            }
            else
                error(AifUtil::getClrErrorMessage());
        }
        else
                error(AifUtil::getClrErrorMessage());
    //AGC memre
        throw error("@SYS327400");

    }

    super();
}

 

Artık hata mesajını açıkça alabiliyordum:

 



Inbound portlara baktım, Windows güvenlik duvarını inceledim, Ax32Serv.exe.config dosyasını inceledim, hiçbirinde sorun görünmüyordu. Client configuration tool bir türlü WCF refresh yapamıyordu:

 


 

Her ne kadar bilgisayarda tek bir AOS olsa da Instance Name yazmayı denedim:



Sorun çözülmüştü: