Blog

Name is Anant Dubey and the intent to create this blog is to discuss the problems and issues that developer face in the dynamics AX development and to share the new things that come up with the new version of AX.

Wednesday, November 29, 2023

Encryption And Decryption Using A Symmetric Key(AES) using x++ in D365FO

 

Encryption And Decryption Using A Symmetric Key(AES) using x++

 Encryption And Decryption Using A Symmetric Key(AES) using x++.


I received a requirement to generate an XML file with encrypted data using a symmetric key. The recipients on the other side will decrypt the text using the same symmetric key. To test this, I used a dummy value such as 'RRR'.

To achieve this, I wrote the code in C# and added the resulting DLL to my project references.


C# Code:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace EncryptionDecryptionUsingSymmetricKey
{
    public class AesOperation
    {
        public static string EncryptString(string key, string plainText)
        {
            byte[] iv = new byte[16];
            byte[] array;

            using (Aes aes = Aes.Create())
            {
                aes.Key = Encoding.UTF8.GetBytes(key);
                aes.IV = iv;

                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
                        {
                            streamWriter.Write(plainText);
                        }

                        array = memoryStream.ToArray();
                    }
                }
            }

            return Convert.ToBase64String(array);// It will convert bytes to string
        }

        public static string DecryptString(string key, string cipherText)
        {
            byte[] iv = new byte[16];
            byte[] buffer = Convert.FromBase64String(cipherText); // It will convert string to bytes
using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(key); aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader((Stream)cryptoStream)) { return streamReader.ReadToEnd(); } } } } } } }



X++ Code:

b14ca5898a4e4133bbce2ea2315a1916

Using EncryptionDecryptionUsingSymmetricKey;

internal final class TestRunnableClass1
{
    public static void main(Args _args)
    {
        str key = "b65ff7654brt8799fghj4ed7892b6798";
 
        str	text = 'RRR';
 
        str encryptedString = AesOperation::EncryptString(key, text);
 
        info(encryptedString);
 
        str decryptedString = AesOperation::DecryptString(key, encryptedString);
 
        info(decryptedString);
    }
}
References   Ref1  Ref2 and https://sheriffayed23.blogspot.com/2023/11/encryption-and-decryption-using.html

Wednesday, October 11, 2023

Get system notification in D365FO

 


  SystemNotificationDataContract notification = new SystemNotificationDataContract();

        notification.Users().value(1, curUserId());

        notification.Title("Delete Operation ended");

        notification.RuleId('StagingCleanup');

        notification.Message(strfmt("Deleted %1 records from TestTable and Total time consumed: %2",countRecord, timeConsumed(fromTime, timeNow())));

        notification.ExpirationDateTime(DateTimeUtil::addHours(DateTimeUtil::utcNow(), 48));


        SystemNotificationsManager::AddSystemNotification(notification);

Tuesday, September 5, 2023

override lookup method in Event handler and chain of command in d365fo

 link - https://dynamics365musings.com/override-an-existing-lookup-method-chain-of-command/


Two Ways To Override An Existing Lookup Method

There are actually two ways to override an existing lookup method.

I will explain both methods. No pun intended. Ultimately, I do not know that one way is preferred over the other. I am more partial to using Chain Of Command, because I find the attribute syntax easier to write. And I tend to put all of my other extension code in a Chain Of Command class. But please use whatever you are most comfortable with.

If you are new to Chain Of Command, I recommend you read this previous article on Chain Of Command Fundamentals.

The Example Scenario

In this section, consider the example that you want to override an existing lookup method on the customer details form. Go to customer form by searching for ‘all customers’ in the search bar. Then select a customer to open the customer details form.

The CustTable form, contains a field that lets a user select the customer group. By default this lookup method shows two columns. The customer group ID and the Description.

Let’s pretend we wish to show a third column. The related Customer Group form contains a field named ‘Terms of payment’. In this example, I will show you how to display additional columns in the lookup.

Override An Existing Lookup Using An Event Handler

In this previous article I showed you how you can use a form event handler to override an existing lookup. The key in this situation, is to use CancelSuperCall.

class TutorialCustTable_Form_Handler
{
    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    [FormControlEventHandler(formControlStr(CustTable, Posting_CustGroup), FormControlEventType::Lookup)]
    public static void Posting_CustGroup_OnLookup(FormControl sender, FormControlEventArgs e)
    {
        SysTableLookup      sysTableLookup  = SysTableLookup::newParameters(tableNum(CustGroup), sender);
        
        // Add the lookup columns
        sysTableLookup.addLookupfield(fieldNum(CustGroup, CustGroup));
        sysTableLookup.addLookupfield(fieldNum(CustGroup, Name));
        sysTableLookup.addLookupfield(fieldNum(CustGroup, PaymTermId));

        // Run the lookup
        sysTableLookup.performFormLookup();

        //cancel the call to super() to prevent the system from trying to show 
        //the lookup form twice and cause an error.
        FormControlCancelableSuperEventArgs cancelableSuperEventArgs = e as FormControlCancelableSuperEventArgs;
        cancelableSuperEventArgs.CancelSuperCall();
    }
}

Override An Existing Lookup Using Chain Of Command

Secondly, we will look at how to override an existing lookup using Chain Of Command. Create a new class, by right clicking on the project and select Add>Class. Name the class TutorialCustTable_Form_Extension.

Now that the class is created, we need to add the attribute to above the class definition to indicate to the compiler that code in this class extends the code in the form CustTable. See this article for how to use chain of command on form methods. Your code should look like this to start.

[ExtensionOf(formStr(CustTable))]
final class TutorialCustTable_Form_Extension
{
}

You may think that we need to add a chain of command class for the form control. However, in this case, we actually need to add form methods. Specifically, add an ‘init’ method and a lookup method. First, we will create a lookup method. And then the ‘init’ method, which gets called when the form first opens, will register our version of the lookup method.

First, add the lookup method. In this case, this is just a method that takes a form control object. It can actually be named something other than just ‘lookup’.

[ExtensionOf(formStr(CustTable))]
final class TutorialCustTable_Form_Extension
{
    public void overridenCustGroupLookup(FormStringControl _formControl)
    {
        SysTableLookup      sysTableLookup  = SysTableLookup::newParameters(tableNum(CustGroup), _formControl);

        // Add the lookup columns
        sysTableLookup.addLookupfield(fieldNum(CustGroup, CustGroup));
        sysTableLookup.addLookupfield(fieldNum(CustGroup, Name));
        sysTableLookup.addLookupfield(fieldNum(CustGroup, PaymTermId));

        // Run the lookup
        sysTableLookup.performFormLookup();
    }

}

Secondly, we need to extend the ‘init’ method to add code that will register the lookup method we just created and associate to the correct form control. The complete code should look like this.

[ExtensionOf(formStr(CustTable))]
final class TutorialCustTable_Form_Extension
{
    public void init()
    {
        next init();

        Posting_CustGroup.registerOverrideMethod(methodStr(FormDataObject, lookup), formMethodStr(CustTable, overridenCustGroupLookup));
    }

    public void overridenCustGroupLookup(FormStringControl _formControl)
    {
        SysTableLookup      sysTableLookup  = SysTableLookup::newParameters(tableNum(CustGroup), _formControl);

        // Add the lookup columns
        sysTableLookup.addLookupfield(fieldNum(CustGroup, CustGroup));
        sysTableLookup.addLookupfield(fieldNum(CustGroup, Name));
        sysTableLookup.addLookupfield(fieldNum(CustGroup, PaymTermId));

        // Run the lookup
        sysTableLookup.performFormLookup();
    }
}

In our example, the form control we needed to change the lookup for, had the ‘Auto Declaration’ property set to Yes. This meant we could simply use the name of the control, Posting_CustGroup, in our code. However, that is not always the case. If you need to reference a control that does not have this property set, you can find the control by adding an extra line of code. You still need to reference the control’s name. But this code still works even when the code does not have the ‘Auto Declaration’ property set to Yes. The ‘init’ method would then look like this.

public void init()
    {
        next init();

        FormStringControl custGroupControl = this.design().controlName(formControlStr(CustTable, Posting_CustGroup));
        custGroupControl.registerOverrideMethod(methodStr(FormDataObject, lookup), formMethodStr(CustTable, overridenCustGroupLookup));
    }

The method named registerOverrideMethod will tell the system what method to use as the lookup code for this form.

Demonstration

After adding all the code, save and compile your project.

In order to see your latest changes, reload the customer details form in your browser.

Finally, test out the overridden lookup method. You show now see all three columns showing in the lookup.

Override lookup method in d365fo

 

Override Lookup AX7 & D365FO



Here is the small example to override lookup ..


copy the lookup event handler of the field and  paste the event into any class




/// <summary>
    ///
    /// </summary>
    /// <param name="sender">receiving value in parameter</param>
    /// <param name="e">receiving value in parameter</param>
    [FormControlEventHandler(formControlStr(PayrollEmployerTaxRegion, Overview_StateId), FormControlEventType::Lookup)]
    public static void Overview_StateId_OnLookup(FormControl sender, FormControlEventArgs e)
    {
           /// write your lookup code here

if control or field already have lookup to we need to cancel parent lookup execution otherwise we will get exception.

below code you can use to cancel parent lookup ---
        FormControlCancelableSuperEventArgs ce = e as FormControlCancelableSuperEventArgs;
        //cancel super() to prevent error.
        ce.CancelSuperCall();
      }


Here is the complete code sample 


/// <summary>
    ///
    /// </summary>
    /// <param name="sender">receiving value in parameter</param>
    /// <param name="e">receiving value in parameter</param>
    [FormControlEventHandler(formControlStr(PayrollEmployerTaxRegion, Overview_StateId), FormControlEventType::Lookup)]
    public static void Overview_StateId_OnLookup(FormControl sender, FormControlEventArgs e)
    {
        SysTableLookup      sysTableLookup  = SysTableLookup::newParameters(tableNum(LogisticsAddressState), sender);
        Query               query           = new Query();

        // Filter lookup to only show US states
        query.addDataSource(tableNum(LogisticsAddressState)).addRange(fieldNum(LogisticsAddressState, CountryRegionId)).value(LogisticsAddressCountryRegion::findByISOCode(SysCountryRegionCode::countryInfo(curext())).CountryRegionId);

        // Sort the lookup by state Id
        query.dataSourceTable(tableNum(LogisticsAddressState)).addOrderByField(fieldNum(LogisticsAddressState, StateId), SortOrder::Ascending);

        // Add fields
        sysTableLookup.addLookupfield(fieldNum(LogisticsAddressState, StateId));
        sysTableLookup.addLookupfield(fieldNum(LogisticsAddressState, Name));

        // Run lookup
        sysTableLookup.parmQuery(query);
        sysTableLookup.performFormLookup();
        FormControlCancelableSuperEventArgs ce = e as FormControlCancelableSuperEventArgs;

        //cancel super() to prevent error.
        ce.CancelSuperCall();
    }


ref: - https://community.dynamics.com/blogs/post/?postid=c7132ede-dd1b-4408-a038-4354f0a2c7be

Thursday, June 1, 2023

Create Label in Visual studio in D365fo

 http://axbackup.com/AxLabelCreator/


http://axbackup.com/AxLabelCreator/release/AxLabelCreator.1.3.1.0.exe

http://axbackup.com/AxLabelCreator/release/AxLabelCreator.1.3.1.0.zip

Monday, May 22, 2023

How to Debug MPOS in D365FO

 click - Start -> developer mode

Enable it





Download Microsot Edge Dev tool preview

https://apps.microsoft.com/store/detail/microsoft-edge-devtools-preview/9MZBFRMZ0MNJ?hl=en-us&gl=US


check console window for any error


Tuesday, November 15, 2022

Table [ax].[GUPITEMBASEPRICE] does not exist, skip creating CONSTRAINT [ax].[GUPITEMBASEPRICE].[I_-1063074925_1429128816] Creating [DataSyncUsersRole] service upgrade in D365 fo on premise

 Error while upgrading service upgrade in On-premise environment in D365


usually this error occurs while upgrading between version 10.0.28 and 10.0.30.. may be appear in future as well

It's not a proper solution, but this workaround helped me...


usually this error occurs at step 57 or step 60.

1. I did manually create table AX.GUPItemBasePrice table in AXDB

(Needed to create this table because upgrade package had removed the "AX" schema and all tables were with "DBO" schema. upgrade package is looking for table in "AX" schema. Got to know from Error Logs)

USE [AXDB]

GO


/****** Object:  Table [ax].[GUPITEMBASEPRICE]    Script Date: 11/15/2022 12:41:45 AM ******/

SET ANSI_NULLS ON

GO


SET QUOTED_IDENTIFIER ON

GO


CREATE TABLE [ax].[GUPITEMBASEPRICE](

[RECID] [bigint] NOT NULL,

[ACTIVATIONDATE] [date] NOT NULL,

[INVENTDIMID] [nvarchar](20) NOT NULL,

[ITEMID] [nvarchar](100) NOT NULL,

[MARKUP] [numeric](32, 16) NOT NULL,

[MARKUPQTY] [numeric](32, 16) NOT NULL,

[PRICE] [numeric](32, 16) NOT NULL,

[PRICEALLOCATEMARKUP] [int] NOT NULL,

[PRICEQTY] [numeric](32, 16) NOT NULL,

[PRICETYPE] [int] NOT NULL,

[UNITID] [nvarchar](10) NOT NULL,

[FROMDATE] [date] NOT NULL,

[TODATE] [date] NOT NULL,

[CREATEDDATETIME] [datetime] NOT NULL,

[DATAAREAID] [nvarchar](4) NOT NULL,

[ROWVERSION] [timestamp] NOT NULL,

 CONSTRAINT [I_GUPITEMBASEPRICE_RECID] PRIMARY KEY CLUSTERED 

(

[RECID] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],

 CONSTRAINT [I_-1063074925_1429128816] UNIQUE NONCLUSTERED 

(

[ITEMID] ASC,

[INVENTDIMID] ASC,

[PRICETYPE] ASC,

[FROMDATE] ASC,

[TODATE] ASC,

[CREATEDDATETIME] ASC,

[DATAAREAID] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_ACTIVATIONDATE]  DEFAULT ('1900-01-01') FOR [ACTIVATIONDATE]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_INVENTDIMID]  DEFAULT ('') FOR [INVENTDIMID]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_ITEMID]  DEFAULT ('') FOR [ITEMID]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_MARKUP]  DEFAULT ((0)) FOR [MARKUP]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_MARKUPQTY]  DEFAULT ((0)) FOR [MARKUPQTY]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_PRICE]  DEFAULT ((0)) FOR [PRICE]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_PRICEALLOCATEMARKUP]  DEFAULT ((0)) FOR [PRICEALLOCATEMARKUP]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_PRICEQTY]  DEFAULT ((0)) FOR [PRICEQTY]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_PRICETYPE]  DEFAULT ((0)) FOR [PRICETYPE]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_UNITID]  DEFAULT ('') FOR [UNITID]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_FROMDATE]  DEFAULT ('1900-01-01') FOR [FROMDATE]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_TODATE]  DEFAULT ('1900-01-01') FOR [TODATE]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_CREATEDDATETIME]  DEFAULT (getutcdate()) FOR [CREATEDDATETIME]

GO


ALTER TABLE [ax].[GUPITEMBASEPRICE] ADD  CONSTRAINT [DF_GUPITEMBASEPRICE_DATAAREAID]  DEFAULT ('dat') FOR [DATAAREAID]

GO


2. Removed all Roles from AXDB (Whichever is allowing to delete)

3. Removed all schemas from AXDB (Whichever is allowing to delete)

4. Execute below query in AXDB

DROP VIEW __RETAIL_PENDING_DEPLOYMENT

5. re-execute the step with below command

AXUpdateInstaller.exe execute -runbookid="Dev-runbook14112022" -rerunstep=60