Asynchronous Operations: To Implement an Asynchronous Operation

The following activity will walk you through the process of implementing a long-running action and executing it asynchronously.

Story

Suppose that you need to create an action that allows users to validate the prices for the repair items added in the table on the Repair Items tab of the Services and Prices (RS203000) form, in the PhoneRepairShop customization project. Suppose that you need to validate the prices by using an external service. This action can potentially take a long time to finish its execution and hence it should be executed asynchronously. You need to define an action in the graph of the form and configure the associated button (on the table toolbar). You need to define the code that will be used to perform the validation of the prices and execute this code asynchronously by using the PXLongOperation.StartOperation method.

Process Overview

In this activity, you will create an action on the table toolbar and execute it asynchronously by performing the following steps:
  1. Adding the IsPriceValidated field that you added in Step 3 of Test Instance for Customization: To Deploy an Instance for Creating Actions to the RSSVRepairItem DAC, and updating the ASPX file of the Services and Prices (RS203000) form. This will make this field visible as the Price Validated column, in the table on the Repair Items tab of the form.
  2. Defining the logic needed to validate the prices of the repair items in a method called ValidatePrices, implementing the ValidateItemPrices action and setting it up the to run the ValidatePrices method asynchronously, and creating the associated Validate Prices button on the table toolbar.
  3. Testing the Validate Prices button and the underlying action.

System Preparation

Make sure that you have configured your instance as described in Test Instance for Customization: To Deploy an Instance for Creating Actions and completed the steps described in the following activities:
  1. Action Definition: To Define an Action for a Form
  2. Action Definition: To Define an Action for a Table

Step 1: Updating the Services and Prices (RS203000) form to Display the Price Validated Column and the Validate Prices Button

To add the IsPriceValidated field to the RSSVRepairItem DAC and update the corresponding ASPX file to respectively show the Price Validated column in the table, and the Validate Prices button on the table toolbar of the Repair Items tab, do the following:
  1. Add the following code to the RSSVRepairItem.cs file after the BasePrice field definition.
            #region IsPriceValidated
            [PXDBBool]
            [PXDefault(false)]
            [PXUIField(DisplayName = "Price Validated", Enabled = false)]
            public virtual bool? IsPriceValidated { get; set; }
            public abstract class isPriceValidated : 
            PX.Data.BQL.BqlBool.Field<isPriceValidated> { }
            #endregion
    Note that the field is set to be disabled on the UI because this field is meant to be updated by an external service. The field is set to false by default.
  2. In the RS203000.aspx file, do the following:
    1. Add the following code inside the <Columns> tag of the <px:PXGridLevel DataMember="RepairItems" > tag.
          <px:PXGridColumn Type="CheckBox" DataField="IsPriceValidated" Width="100" >
          </px:PXGridColumn>
      This code has added the Price Validated column, which is represented by the IsPriceValidated DAC field, to the table on the Repair Items tab.
    2. Add the following code in the <px:PXTabItem Text="Repair Items"> tag after the Levels closing tag.
                  <ActionBar>
                      <CustomItems>
                          <px:PXToolBarButton Text="ValidateItemPrices">
                              <AutoCallBack Command="ValidateItemPrices" Target="ds" />
                          </px:PXToolBarButton>
                      </CustomItems>
                  </ActionBar>
      In this code, you have defined an action bar on the table toolbar of the Repair Items tab and have added the Validate Prices button to it.
  3. Save your changes.

Step 2: Defining the Logic Used to Validate Prices

You should define the method in which the repair items prices are validated, and then you can call this method in the PXLongOperation.StartOperation method.

Since the objective of this activity is to execute a long-running operation asynchronously by using the PXLongOperation.StartOperation method, you will not be focusing on the specifics of the logic of the long-running operation itself —that is, you will not be writing the code for connecting to an actual external service, making a request and parsing the received result. Instead, you will use the Thread.Sleep() method to create a delay in the execution of the method to simulate connecting to an external service. You will simply pass the repair items to the ValidatePrices method and set the IsPriceValidated field for each repair item to true. This will change the state of the check box in the Price Validated column of the Repair Items tab to be selected for each repair item and simulate a successful validation from an external service.

To define the method in which the repair items prices are validated, do the following:

  1. Add the following using directives to the RSSVRepairPriceMaint.cs file (if they have not been added yet).
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    Tip:
    Instead of adding the using directives manually, you can add them with the help of the Quick Actions and Refactorings feature of Visual Studio after you define the method in the next instruction.
  2. Add the following static method, ValidatePrices, to the RSSVRepairPriceMaint graph. The ValidatePrices method validates the repair items prices for the selected record on the Services and Prices (RS203000) form.
            private static void ValidatePrices(RSSVRepairPrice repairPriceItem)
            {
                /* Create an instance of the RSSVRepairPriceMaint graph and set 
                   the Current property of its RepairPrices view.*/
                var priceMaint = PXGraph.CreateInstance<RSSVRepairPriceMaint>();
                priceMaint.RepairPrices.Current = priceMaint.RepairPrices.
                 Search<RSSVRepairPrice.serviceID, RSSVRepairPrice.deviceID>
                 (repairPriceItem.ServiceID, repairPriceItem.DeviceID);
    
                /* Set a delay to mimic connecting to an external service to validate 
                   the repair item prices.
                   In a real world scenario, you would connect to an actual external 
                   service and make an API request to validate the prices for 
                   the repair items.*/
                Thread.Sleep(3000);
    
                /* Update the Price Validated field for each repair item on the 
                   Repair Items tab:
                   Here we are assuming that the validation was successful from 
                   the external service and are setting IsPriceValidated to 
                   true for each repair item.*/     
                foreach (RSSVRepairItem item in priceMaint.RepairItems.Select())
                {
                    // Set IsPriceValidated to true for each repair item.
                    item.IsPriceValidated = true;
                    // Update the cache with the above change for each repair item.
                    priceMaint.RepairItems.Update(item);
                }
                /*Trigger the Save action to save the changes stored in the cache 
                  to the database.*/
                priceMaint.Actions.PressSave();
            }

In the method above, you first create an instance of the RSSVRepairPriceMaint graph. You then set the current property of the RepairPrices view of this graph instance to the parameter of type RSSVRepairPrice that was passed into the ValidatePrices method.

You then use the Thread.Sleep(3000) method call to pause the execution of the method to simulate a long-running operation that is connecting to an external service. You then loop through each repair item of the selected record on the Services and Prices form and set its IsPriceValidated property to true to indicate that its price has been validated. Finally, you update the cache and save the changes to the database.

Step 3: Defining the ValidateItemPrices Action

The ValidateItemPrices action defines the underlying action for the Validate Prices button on the table toolbar of the Repair Items tab, and invokes the PXLongOperation.StartOperation method that executes the ValidatePrices method added in Step 2, asynchronously.

To define the ValidateItemPrices action, add the following code to the RSSVRepairPriceMaint graph.

        #region Actions
        public PXAction<RSSVRepairPrice> ValidateItemPrices;
        [PXButton(DisplayOnMainToolbar = false, CommitChanges = true)]
        [PXUIField(DisplayName = "Validate Prices", Enabled = true)]
        protected virtual IEnumerable validateItemPrices(PXAdapter adapter)
        {
            // Populate a local list variable.
            List<RSSVRepairPrice> list = new List<RSSVRepairPrice>();
            foreach (RSSVRepairPrice repairItemPrice in adapter.Get<RSSVRepairPrice>())
            {
                list.Add(repairItemPrice);
            }

            // Trigger the Save action to save changes in the database.
            Actions.PressSave();

            var repairPriceItem = RepairPrices.Current;
            /*Execute ValidatePrices method asynchronously 
              using PXLongOperation.StartOperation*/
            PXLongOperation.StartOperation(this, () => ValidatePrices(repairPriceItem));

            // Return the local list variable.
            return list;
        }
        #endregion
Note:
To perform a background operation, an action method needs to have a parameter of the PXAdapter type and return IEnumerable.

In the ValidateItemPrices method, you compose a list of services and prices by using the adapter.Get method, and invoke the Actions.PressSave action. Because the return of the adapter.Get method does not include data that has not been saved on the form, by calling the PressSave method, you update the records in the composed list.

Then you use the PXLongOperation.StartOperation() method to validate the prices of the repair items on the Repair Items tab for the record that is selected on the Services and Prices (RS203000) form. You do this by invoking the ValidatePrices method within the method that you pass to StartOperation().

Finally, you return the list of services and prices.

Step 4: Testing the Validate Prices Button and the Associated Action

To test the Validate Prices button and the underlying action, do the following:

  1. Rebuild the PhoneRepairShop_Code project in Visual Studio, and publish the customization project in the Customization Project Editor.
  2. In MYOB Acumatica, open the Services and Prices (RS203000) form.
  3. Open any services and prices record —that is, any services and prices record that does not have the check box selected in the Price Validated column for any of the repair items on the Repair Items tab.

    Notice that the Validate Prices button is available on the table toolbar of the Repair Items tab.

  4. On the table toolbar, click Validate Prices.

    A notification appears indicating the status of the processing, as shown in the following screenshot.

    Figure 1. Validation of the prices of repair items


    When the process is complete, the check box for each repair item is selected in the Price Validated column of the Repair Items tab, as shown in the following screenshot.

    Figure 2. Update of the Price Validated column