You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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; }
 
}

问题分析与解决方案

核心原因

  1. 事件订阅未清理:组件销毁时未移除OnFieldChanged事件订阅,导致旧组件实例的事件处理程序被错误触发,此时sender指向错误对象。
  2. 异步方法误用:DoCalculateFormulas标记为async void,Blazor中这种写法会导致上下文混乱,触发意外状态变更通知。
  3. 事件来源未校验:未确认事件是否来自当前组件的EditContext,导致其他组件的变更触发当前事件处理逻辑。

修复步骤

  1. 清理事件订阅
    组件实现IDisposable接口,在销毁时移除事件监听:

    @implements IDisposable
    
    // 页面内容...
    
    @code{
        // 原有代码...
    
        public void Dispose()
        {
            if (_editContextProduct != null)
            {
                _editContextProduct.OnFieldChanged -= ProductEditContext_OnFieldChanged;
            }
        }
    }
    
  2. 校验事件来源
    在事件处理程序中确认sender为当前EditContext,避免处理无关事件:

    private void ProductEditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
    {
        if (sender != _editContextProduct) return;
        DoCalculateFormulas();
    }
    
  3. 修正异步方法
    将async void改为async Task,避免上下文混乱:

    private async Task ProductEditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
    {
        if (sender != _editContextProduct) return;
        await DoCalculateFormulas();
    }
    
    private async Task DoCalculateFormulas()
    {
        // 原有计算逻辑不变
    }
    
  4. 手动控制状态变更
    在公式计算完成后手动调用StateHasChanged(),减少不必要的重渲染:

    private void DoCalculateFormulas()
    {
        // 原有计算逻辑...
        
        StateHasChanged();
    }
    

内容的提问来源于stack exchange,提问作者KleberBueno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 06:15:12