Blazor EditContext触发OnFieldChanged事件时出现异常行为
Blazor页面EditContext异常触发OnFieldChanged问题
问题现象
单个Blazor页面包含多个自定义组件,其中一个组件拥有独立的EditContext和关联Model。为了在字段值变更时执行公式计算,给OnFieldChanged事件添加了监听,但出现以下异常:
- 组件内部局部变量变更时也会触发该事件,且事件参数中的Model变成组件本身而非上下文Model
- 问题并非首次渲染出现,在3-4次公式计算后开始发生
- 引发无限循环:公式值变更触发事件,重新计算公式,往复循环
相关代码
页面组件代码
<EditForm EditContext="@_editContextProduct" style="margin:0px"> <DataAnnotationsValidator/> //ALL MY FIELDS HERE </EditForm > @code{ private CommercialEvaluationProductContextModel _commercialEvaluationProductContextModel = new (); private EditContext _editContextProduct; protected async override Task OnInitializedAsync() { _editContextProduct = new EditContext(_commercialEvaluationProductContextModel); _editContextProduct.OnFieldChanged += ProductEditContext_OnFieldChanged; } //formulas declaration private int _formulaPacksPerStore =0; private decimal _formulaSizeofPrice =0; private decimal _formulaRetailerMargin =0; private decimal _formulaRevenue =0; private decimal _formulaMargin =0; private decimal _formulaCogs =0; private int _formulaPacksPerRun = 0; private int _formulaProductionFrequency = 0; private decimal _formulaCogsPerPack = 0; private void ProductEditContext_OnFieldChanged(object sender,FieldChangedEventArgs e) { DoCalculateFormulas(); } private async void DoCalculateFormulas() { _formulaPacksPerStore = _commercialEvaluationProductContextModel.ExpectedAnnualPackVolume / Math.Max(1,_commercialEvaluationProductContextModel.ListingDuration) / Math.Max(1,_commercialEvaluationProductContextModel.DistributionValue); _formulaSizeofPrice = _commercialEvaluationProductContextModel.RetailSalePrice * _commercialEvaluationProductContextModel.ExpectedAnnualPackVolume; _formulaRetailerMargin = _formulaSizeofPrice * (_commercialEvaluationProductContextModel.RetailerMargin / 100.0M); _formulaRevenue = _formulaSizeofPrice - _formulaRetailerMargin; _formulaMargin = _formulaRevenue * (_commercialEvaluationProductContextModel.Margin/100.0M); _formulaCogs = _formulaRevenue - _formulaMargin; _formulaPacksPerRun = _commercialEvaluationProductContextModel.ExpectedAnnualPackVolume / Math.Max(1,_commercialEvaluationProductContextModel.ListingDuration); _formulaProductionFrequency = 365/Math.Max(1,_commercialEvaluationProductContextModel.ListingDuration); _formulaCogsPerPack = _formulaKepakCogs / Math.Max(1,_commercialEvaluationProductContextModel.ExpectedAnnualPackVolume); } }
模型类代码
public class CommercialEvaluationProductContextModel { public int Id { get; set; } [StringLength(maximumLength: 200, MinimumLength = 3)] [Required] public string Name { get; set; } [Required(ErrorMessage = "Choose the product state")] [Range(1, 99900, ErrorMessage = "Invalid product state")] public Int16 ProductStateId { get; set; } public ProductState? ProductState { get; set; } [Range(1, 99900, ErrorMessage = "Choose the listing type")] public Int16 ListingTypeId { get; set; } public ListingType? ListingType { get; set; } [Range(1, 99900, ErrorMessage = "Choose duration of listing")] public Int16 ListingTimeIntervalId { get; set; } public TimeInterval? TimeInterval { get; set; } [Required(ErrorMessage = "Inform duration of listing")] [Range(1, 9999999, ErrorMessage = "Invalid duration of listing")] public int ListingDuration { get; set; } [Range(1, 32000, ErrorMessage = "Invalid distribution")] public Int16 DistributionTypeId { get; set; } public DistributionType? DistributionType { get; set; } [Required(ErrorMessage = "Inform the distribution value")] [Range(1, 99999999, ErrorMessage = "Invalid duration of listing")] public int DistributionValue { get; set; } [Required(ErrorMessage = "Inform the expected annual pack volume")] [Range(1, 99999999, ErrorMessage = "Invalid expected annual pack volume")] public int ExpectedAnnualPackVolume { get; set; } [Range(1, 99999, ErrorMessage = "Invalid currency")] public Int16 CurrencyId { get; set; } public Currency? Currency { get; set; } [Range(typeof(decimal), "0.1", "99999999999.99", ErrorMessage = "Invalid retail sales price")] public decimal RetailSalePrice { get; set; } [Range(typeof(decimal), "0.1", "99999999999.99", ErrorMessage = "Invalid margin")] public decimal Margin{ get; set; } [Range(typeof(decimal), "0.1", "99999999999.99", ErrorMessage = "Invalid retail margin")] public decimal RetailerMargin { get; set; } [MinLength(3, ErrorMessage = "Minimum of 3 characters")] public string SalesRational { get; set; } [MinLength(3, ErrorMessage = "Minimum of 3 characters")] public string CategoryRational { get; set; } [Range(1, 99900, ErrorMessage = "Invalid expected annual pack volume")] public Int16 EstimatedProductWeightMeasureUnitId { get; set; } public MeasureUnit? MeasureUnit { get; set; } [Range(typeof(decimal), "0.1", "99999999999.99", ErrorMessage = "Invalid Estimated Product Weight")] public decimal EstimatedProductWeight { get; set; } [Range(1, 99900, ErrorMessage = "Invalid expected annual pack volume")] public int NumberPouchesperInner { get; set; } public string StatusId { get; set; } }
问题分析与解决方案
核心原因
- 事件订阅未清理:组件销毁时未移除
OnFieldChanged事件订阅,导致旧组件实例的事件处理程序被错误触发,此时sender指向错误对象。 - 异步方法误用:
DoCalculateFormulas标记为async void,Blazor中这种写法会导致上下文混乱,触发意外状态变更通知。 - 事件来源未校验:未确认事件是否来自当前组件的EditContext,导致其他组件的变更触发当前事件处理逻辑。
修复步骤
清理事件订阅
组件实现IDisposable接口,在销毁时移除事件监听:@implements IDisposable // 页面内容... @code{ // 原有代码... public void Dispose() { if (_editContextProduct != null) { _editContextProduct.OnFieldChanged -= ProductEditContext_OnFieldChanged; } } }校验事件来源
在事件处理程序中确认sender为当前EditContext,避免处理无关事件:private void ProductEditContext_OnFieldChanged(object sender, FieldChangedEventArgs e) { if (sender != _editContextProduct) return; DoCalculateFormulas(); }修正异步方法
将async void改为async Task,避免上下文混乱:private async Task ProductEditContext_OnFieldChanged(object sender, FieldChangedEventArgs e) { if (sender != _editContextProduct) return; await DoCalculateFormulas(); } private async Task DoCalculateFormulas() { // 原有计算逻辑不变 }手动控制状态变更
在公式计算完成后手动调用StateHasChanged(),减少不必要的重渲染:private void DoCalculateFormulas() { // 原有计算逻辑... StateHasChanged(); }
内容的提问来源于stack exchange,提问作者KleberBueno
相关产品推荐
相关产品推荐

