Wednesday, December 6, 2017

Create and run Custom Service in D365FO (AX7) Part 2

In the previous article, I created two services and checked their work.

Let's upgrade the work of our first service in such a way that it could run as many threads as possible, each of which will generate a unique text string, and the service AUTOMATICALLY will call our second service and transmit the generated string. It sounds interesting. Let's get started

I changed the first service in such a way that now it takes as the parameter the service that you want to call, the number of threads, the time of the sleep of each stream. Also, the service now returns a string with the list of thread identifiers it created. I also changed the name of the method, so before you start the service, you need to change the name of the method that is attached to the service operation.

About how to create asynchronous methods in AX7 read on my blog.


class GenerateUniqueString
{
    public str runAsync(str callbackService, int threads, int sleepMS)
    {
        List tasks = new List(Types::Class);
        for(int i = 0; i < threads; i++)
        {
            System.Threading.Tasks.Task task = Global::runAsync(
                    classNum(GenerateUniqueString),
                    staticMethodStr(GenerateUniqueString, methodRunAsync),
                    [callbackService, int2Str(i), sleepMS],
                    System.Threading.CancellationToken::None,
                    classNum(GenerateUniqueString),
                    staticMethodStr(GenerateUniqueString, methodRunAsyncResult));
            tasks.addEnd(task);
        }
        str resultStr = "";
        ListEnumerator enumerator = tasks.getEnumerator();
        while(enumerator.moveNext())
        {
            System.Threading.Tasks.Task current = enumerator.current();
            resultStr += int2Str(current.Id) + ",";
        }

        return resultStr;
    }

    public static container methodRunAsync(container _params)
    {
        str callbackService = conPeek(_params, 1);
        str i = conPeek(_params, 2);
        int sleepMS = conPeek(_params, 3);
        str data = "Test_Async_data_" + i + "_" + datetime2Str(DateTimeUtil::getSystemDateTime());
        
        System.Threading.Thread::Sleep(sleepMS);
        return [callbackService, data];
    }

    public static void methodRunAsyncResult(AsyncTaskResult _result)
    {
        container returnValue = _result.getResult();
        str callbackService, data;
        [callbackService, data] = returnValue;
    }

}

Also, I slightly changed the second service, now it creates a file based on a unique string that came in parameters

class SaveStringToDIsc
{
    public void downloadFile(str parameters)
    {
        TextIo file;
        str fileNameNext = parameters;

        fileNameNext = strReplace(fileNameNext, "/", "_");
        fileNameNext = strReplace(fileNameNext, " ", "_");
        fileNameNext = strReplace(fileNameNext, ":", "_");

        FileName filename = @"C:\Temp\Downloads\" + fileNameNext + ".txt";
        FileIoPermission permission;
        #File
        
        try
        {
            permission = new FileIoPermission(filename, #io_write);
            permission.assert();

            file = new TextIo(filename, #io_write);

            if (!file)
            {
                throw Exception::Error;
            }
            file.write(parameters);
        }
        catch(Exception::Error)
        {
            error("You do not have access to write the file to the selected folder");
        }
        CodeAccessPermission::revertAssert();
    }

}
Now we need to add a request created using the X++ code that we did with the help of Fiddler in the previous article.
At the official GitHub MS account, I found an example of a C# code that allows you to create request and decide to create a small library that can generate requests.
And adding reference it to our X ++ project POST request can be done as follows

public static void methodRunAsyncResult(AsyncTaskResult _result)
    {
        container returnValue = _result.getResult();
        str callbackService, data;
        [callbackService, data] = returnValue;

        OAuthRequestManager.OAuthRequestManager requestManager = new OAuthRequestManager.OAuthRequestManager();
        str contentJson = Newtonsoft.Json.JsonConvert::SerializeObject([data]);
        requestManager.SendPOSTRequest(callbackService, contentJson);
    }

I am creating a new object like OAuthRequestManager - this is my mini library, then I create serialized string JSON type array, for this, I pre-converted my one parameter to the container

The mini-library works in such a way that it is possible to call services with parameters, in which parameters need to be transmitted in the form of a JSON that contains an array of arguments for the service to be called.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;

namespace OAuthRequestManager
{
    public class OAuthRequestManager
    {
        public string GenerateContentByService(string uri, string jsonList)
        {
            HttpWebResponse response;
            string responseString = string.Empty;

            response = SendGETRequest(uri);

            if (response != null)
            {
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader streamReader = new StreamReader(responseStream))
                    {
                        responseString = streamReader.ReadToEnd();
                    }
                }
            }

            GetResponseDataContract contract = JsonConvert.DeserializeObject(responseString);
            var contentObj = JsonConvert.DeserializeObject>(jsonList);
            var resultObj = new Dictionary();

            for (int i = 0; i < contract.Parameters.Count; i++)
            {
                resultObj.Add(contract.Parameters[i].Name, contentObj[i]);
            }

            string result = JsonConvert.SerializeObject(resultObj);

            return result;
        }

        public string GetResponseContent(HttpWebResponse response)
        {
            if (response == null)
            {
                throw new ArgumentNullException("response");
            }

            string responseFromServer = string.Empty;

            using (Stream responseStream = response.GetResponseStream())
            {
                using (StreamReader streamReader = new StreamReader(responseStream))
                {
                    responseFromServer = streamReader.ReadToEnd();
                }
            }

            return responseFromServer;
        }

        public HttpWebResponse SendPOSTRequest(string uri, string jsonList)
        {
            HttpWebRequest request = GeneratePOSTRequest(uri, jsonList);
            return GetResponse(request);
        }

        public HttpWebResponse SendGETRequest(string uri)
        {
            HttpWebRequest request = GenerateGETRequest(uri);
            return GetResponse(request);
        }

        public HttpWebResponse SendRequest(string uri, string jsonList, string method)
        {
            HttpWebRequest request = GenerateRequest(uri, jsonList, method);
            return GetResponse(request);
        }

        public HttpWebRequest GenerateGETRequest(string uri)
        {
            return GenerateRequest(uri, null, "GET");
        }

        public HttpWebRequest GeneratePOSTRequest(string uri, string jsonList)
        {
            return GenerateRequest(uri, jsonList, "POST");
        }

        internal HttpWebRequest GenerateRequest(string uri, string jsonList, string method)
        {
            if (uri == null)
            {
                throw new ArgumentNullException("uri");
            }

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            request.Method = method;
            request.Headers[OAuthHelper.OAuthHeader] = OAuthHelper.GetAuthenticationHeader();
 
            if (method == "POST" && !string.IsNullOrEmpty(jsonList))
            {
                var requestContractString = GenerateContentByService(uri, jsonList); 

                using (var stream = request.GetRequestStream())
                {
                    using (var writer = new StreamWriter(stream))
                    {
                        writer.Write(requestContractString);
                    }
                }
            }
            else
            {
                request.ContentLength = 0;
            }

            return request;
        }

        internal HttpWebResponse GetResponse(HttpWebRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            return response;
        }
    }
}


using System;

namespace OAuthRequestManager
{
    public class ClientConfiguration
    {
        public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } }

        public static ClientConfiguration OneBox = new ClientConfiguration()
        {
            UriString = "Your Instance Url",
            UserName = "",
            Password = "",
            ActiveDirectoryResource = "Your Instance Url",
            ActiveDirectoryTenant = "https://login.windows.net/Your Tenant/",
            ActiveDirectoryClientAppId = "Your Client Id",
            ActiveDirectoryClientAppSecret = "Your Secret Key",
        };

        public string UriString { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public string ActiveDirectoryResource { get; set; }
        public String ActiveDirectoryTenant { get; set; }
        public String ActiveDirectoryClientAppId { get; set; }
        public string ActiveDirectoryClientAppSecret { get; set; }
    }
}


using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace OAuthRequestManager
{
    public class OAuthHelper
    {
        /// 
        /// The header to use for OAuth.
        /// 
        public const string OAuthHeader = "Authorization";

        /// 
        /// Retrieves an authentication header from the service.
        /// 
        /// The authentication header for the Web API call.
        public static string GetAuthenticationHeader()
        {
            string aadTenant = ClientConfiguration.Default.ActiveDirectoryTenant;
            string aadClientAppId = ClientConfiguration.Default.ActiveDirectoryClientAppId;
            string aadResource = ClientConfiguration.Default.ActiveDirectoryResource;

            AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
            AuthenticationResult authenticationResult;

            string aadClientAppSecret = ClientConfiguration.Default.ActiveDirectoryClientAppSecret;
            var creadential = new ClientCredential(aadClientAppId, aadClientAppSecret);
            authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, creadential).Result;

            // Create and get JWT token
            return authenticationResult.CreateAuthorizationHeader();
        }
    }
}


using System.Collections.Generic;

namespace OAuthRequestManager
{
    public class GetResponseDataContract
    {
        public IList Parameters { get; set; }
        public GetReposnseType Return { get; set; }
    }
}


namespace OAuthRequestManager
{
    public class GetReposnseType
    {
        public string Name { get; set; }
        public string Type { get; set; }
    }
}


For use this C# code you have to create new C# Class Library with classes which you can see. Then add Nuget Package called Microsoft.Identity.Clients.ActiveDirectory. Then configure ClientConfiguration class. After that build your library adn add to X++ project.

1 comment: