首页 > 解决方案 > 在事件处理程序中运行长时间操作

问题描述

我需要使用 3rd 方 API 对客户位置地址进行一些地址验证,以确定该地址是住宅地址还是商业地址。每当更改地址字段时,都应运行此验证。换句话说,验证应该在Address_RowUpdated事件处理程序中运行。

因为该函数正在调用第 3 方 API,所以我认为它应该在一个单独的线程中完成,PXLongOperation这样它就不会阻止地址保存,并且如果 API 不可用或返回错误,它会优雅地失败。

但是,我不确定是否支持在事件处理程序中运行长操作的体系结构,或者其他方法是否会更好。

这是我的代码。

public class CustomerLocationMaint_Extension : PXGraphExtension<CustomerLocationMaint>
{
    protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
    {
        PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
        if (row != null)
        {
            Location location = this.Base.Location.Current;
            PXCache locationCache = Base.LocationCurrent.Cache;

            PXLongOperation.StartOperation(Base, delegate
            {
                RunCheckResidential(location, locationCache); 
            });

            this.Base.LocationCurrent.Cache.IsDirty = true; 
        }
    }

    protected void RunCheckResidential(Location location, PXCache locationCache)
    {
        string messages = "";

        PX.Objects.CR.Address defAddress = PXSelect<PX.Objects.CR.Address,
            Where<PX.Objects.CR.Address.addressID, Equal<Required<Location.defAddressID>>>>.Select(Base, location.DefAddressID);

        FValidator validator = new FValidator();
        AddressValidationReply reply = validator.Validate(defAddress);
        AddressValidationResult result = reply.AddressResults[0];

        bool isResidential = location.CResedential ?? false; 
        if (result.Classification == FClassificationType.RESIDENTIAL) 
        {
            isResidential = true;
        } else if (result.Classification == FClassificationType.BUSINESS)
        {
            isResidential = false;
        } else
        {
            messages += "Residential classification is: " + result.Classification + "\r\n";
        }
        location.CResedential = isResidential;

        locationCache.Update(location);
        Base.LocationCurrent.Update(location);
        Base.Actions.PressSave();

        // Display relevant messages
        if (reply.HighestSeverity == NotificationSeverityType.SUCCESS)
            String addressCorrection = validator.AddressCompare(result.EffectiveAddress, defAddress);
            if (!string.IsNullOrEmpty(addressCorrection))
                messages += addressCorrection;
        }

        PXSetPropertyException message = new PXSetPropertyException(messages, PXErrorLevel.Warning);
        PXLongOperation.SetCustomInfo(new LocationMessageDisplay(message));
        //throw new PXOperationCompletedException(messages); // Shows message if you hover over the success checkmark, but you have to hover to see it so not ideal

    }

    public class LocationMessageDisplay : IPXCustomInfo
    {
        public void Complete(PXLongRunStatus status, PXGraph graph)
        {
            if (status == PXLongRunStatus.Completed && graph is CustomerLocationMaint)
            {
                ((CustomerLocationMaint)graph).RowSelected.AddHandler<Location>((sender, e) =>
                {
                    Location location = e.Row as Location;
                    if (location != null)
                    {
                        sender.RaiseExceptionHandling<Location.cResedential>(location, location.CResedential, _message);
                    }
                });
            }
        }

        private PXSetPropertyException _message;

        public LocationMessageDisplay(PXSetPropertyException message)
        {
            _message = message;
        }
    }
}

更新 - 新方法

如建议的那样,此代码现在在 Persist 方法中调用 LongOperation。

protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
    {
        PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
        if (row != null)
        {
            Location location = Base.Location.Current;
            LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
            locationExt.UsrResidentialValidated = false;
            Base.LocationCurrent.Cache.IsDirty = true; 
        }
    }   

public delegate void PersistDelegate();
    [PXOverride]
    public virtual void Persist(PersistDelegate baseMethod)
    {
        baseMethod();

        var location = Base.Location.Current;
        PXCache locationCache = Base.LocationCurrent.Cache;
        LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);

        if (locationExt.UsrResidentialValidated == false)
        {
            PXLongOperation.StartOperation(Base, delegate
            {
                CheckResidential(location);
            });
        }
    }

public void CheckResidential(Location location)
    {
        CustomerLocationMaint graph = PXGraph.CreateInstance<CustomerLocationMaint>();

        graph.Clear();
        graph.Location.Current = location;

        LocationExt locationExt = location.GetExtension<LocationExt>();
        locationExt.UsrResidentialValidated = true;

        try
        {
          // Residential code using API (this will change the value of the location.CResedential field)
        } catch (Exception e)
        {
            throw new PXOperationCompletedWithErrorException(e.Message);
        }

        graph.Location.Update(location);
        graph.Persist();
    }

标签: acumatica

解决方案


PXLongOperation 旨在用于 PXAction 回调的上下文中。这通常由菜单项或按钮控件启动,包括保存等内置操作。

每当网页中的值发生变化时使用它是一种反模式。只有在值被持久化(通过 Save 操作)或另一个 PXAction 事件处理程序时才应该使用它。当用户单击按钮或菜单项而不是更改值时,您应该处理长时间运行的验证。

例如,内置的验证地址功能仅在用户单击验证地址按钮时运行,如果需要验证请求,它还会在保存操作上下文中调用的持久事件中运行,以在验证失败时取消保存。

这样做是为了确保用户期望表单/网格值字段中的简单更改不会导致长时间的验证等待时间,从而导致用户相信网页没有响应。当用户点击保存或特定的操作按钮时,预期更长的等待时间被认为是更合理的。

话虽如此,不建议但可以将您的 PXLongOperation 调用包装在一个虚拟 Action 中并异步单击不可见的 Action 按钮以从任何事件处理程序(初始化除外)在适当的上下文中运行长操作:

using PX.Data;
using System.Collections;

namespace PX.Objects.SO
{
    public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
  {
      public PXAction<SOOrder> TestLongOperation;

      [PXUIField(DisplayName = "Test Long Operation", Visible = false, Visibility = PXUIVisibility.Invisible)]
      [PXButton]
      public virtual IEnumerable testLongOperation(PXAdapter adapter)
      {
        PXLongOperation.StartOperation(Base, delegate () 
        { 
            System.Threading.Thread.Sleep(2000);
            Base.Document.Ask("Operation Done", MessageButtons.OK);
        });

        return adapter.Get();
      }

      public void SOOrder_OrderDesc_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
      {
        if (!PXLongOperation.Exists(Base.UID))
        {
            // Calling Action Button asynchronously so it can run in the context of a PXAction callback
            Base.Actions["TestLongOperation"].PressButton();
        }
      }
  }
}

推荐阅读