acumatica - 在事件处理程序中运行长时间操作
问题描述
我需要使用 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();
}
解决方案
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();
}
}
}
}
推荐阅读
- java - 即使在设置 server.servlet.contextPath 之后,弹簧执行器也会出现 404 Not found 错误
- regex - 需要从 perl 中最后捕获的字符串返回捕获组的值
- javascript - 为什么我的画布游戏代码从 Javascript 中的构造函数的模块返回“未定义”?
- python - 如何从python字典中删除一些值
- c - MSP432 LaunchPad 按钮初始化
- excel - 将验证列表复制到新书时如何保留另一张纸上的验证列表
- reactjs - 尝试使用 UseEffect 在 react.js 中创建倒计时(日:小时:分钟:秒)
- python - Pandas:如何在 Dataframe 中添加表示 2 个属性交集的列
- api - 在 DocuSign 中,信封状态报告(在控制台 -> 报告部分)我们得到一个字段总页数;如何通过 DocuSign API 获得相同的?
- node.js - 如何从前端保存和发送 JWT 令牌