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

Blazor/.NET8任务执行时阻止OnValidSubmit重复触发

Blazor/.NET8 注册组件重复提交问题解决

问题描述

我开发了一个Blazor/.NET8 Web应用,在用户注册组件中遇到两个核心问题:

  • 无法在任务运行时阻止OnValidSubmit重复触发
  • 无法禁用提交按钮防止重复点击

尝试过的方案及异常表现

首先尝试用私有bool变量控制任务执行,但提交操作会将该bool重置为false;之后创建单例StateService持久化状态,结果依旧。

StateService代码

namespace TripSuite.Service
{
    public interface IStateService
    {
        bool IsSubmitting { get; set; }
    }
    public class StateService : IStateService
    {
        public bool IsSubmitting { get; set; } = false;
    }
}

服务注册(program.cs)

builder.Services.AddSingleton<IStateService, StateService>();

注册组件代码

<EditForm Model="Input" asp-route-returnUrl="@ReturnUrl" method="post" OnValidSubmit="HandleSubmit" FormName="register">
            <DataAnnotationsValidator />
            <hr />
            <ValidationSummary class="text-danger" role="alert" />
            <div class="form-floating mb-3">
                <InputText @bind-Value="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
                <label for="email">Email</label>
                <ValidationMessage For="() => Input.Email" class="text-danger" />
            </div>
            <div class="form-floating mb-3">
                <InputText type="password" @bind-Value="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
                <label for="password">Password</label>
                <ValidationMessage For="() => Input.Password" class="text-danger" />
            </div>
            <div class="form-floating mb-3">
                <InputText type="password" @bind-Value="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
                <label for="confirm-password">Confirm Password</label>
                <ValidationMessage For="() => Input.ConfirmPassword" class="text-danger" />
            </div>

    <MudButton ButtonType="ButtonType.Submit" Disabled="@stateService.IsSubmitting">Register</MudButton>
        </EditForm>

组件后台代码

private async void HandleSubmit()
{
    if (!stateService.IsSubmitting)
    {
        stateService.IsSubmitting = true;
        StateHasChanged();
        await RegisterUser();
    }
}

异常细节

注释掉await RegisterUser();后,UI表现正常,提交按钮会被禁用;但保留该调用时,重复提交问题依旧。RegisterUser方法用于创建用户并设置默认值:

public async Task RegisterUser()
{
    var user = CreateUser();
    await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
    var emailStore = GetEmailStore();
    await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
    var result = await UserManager.CreateAsync(user, Input.Password);

    if (!result.Succeeded)
    {
        identityErrors = result.Errors;
        return;
    }

    var userId = await UserManager.GetUserIdAsync(user);
    var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
    var callbackUrl = NavigationManager.GetUriWithQueryParameters(
        NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
        new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });

    await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));

    var usersCount = await UserManager.Users.CountAsync();
    if (usersCount == 1) // 第一个用户刚创建
    {
        var adminRoleExists = await RoleManager.RoleExistsAsync("Admin");
        if (!adminRoleExists)
        {
            await RoleManager.CreateAsync(new IdentityRole("Admin"));
        }
        await UserManager.AddToRoleAsync(user, "Admin");
        await UserManager.AddClaimAsync(user, new Claim("Permission", "CanAccessEverything"));
    }

    var newUserSettings = new UserSettings
    {
        UserID = userId,
        SelectedTheme = "Dark"
    };

    var newEntity = new Entities
    {
        Email = user.Email,
        UserID = userId,
        EntityType = Enums.EntityTypes.WebUser
    };

    DB.Entities.Add(newEntity);
    DB.UserSettings.Add(newUserSettings);
    await DB.SaveChangesAsync();

    if (UserManager.Options.SignIn.RequireConfirmedAccount)
    {
        RedirectManager.RedirectTo(
            "Account/RegisterConfirmation",
            new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl });
    }
    await SignInManager.SignInAsync(user, isPersistent: false);
         
    RedirectManager.RedirectTo(ReturnUrl);
}

补充测试

新建服务器端项目,用Identity脚手架生成的注册组件测试,依然能重复触发OnValidSubmit:

<div class="col-md-4">
    <StatusMessage Message="@Message" />
    <EditForm Model="Input" asp-route-returnUrl="@ReturnUrl" method="post" OnValidSubmit="RegisterUser" FormName="register">
        <button type="submit" class="w-100 btn btn-lg btn-primary" disabled="@_isSubmitting">Register</button>
    </EditForm>
</div>
private bool _isSubmitting = false;

public async Task RegisterUser(EditContext editContext)
{
    if (!_isSubmitting)
    {
        _isSubmitting = true;
        await Task.Delay(100);
        // 注册逻辑
        _isSubmitting = false;
    }
    else
    {
        return;
    }
}

问题原因及修复方案

核心原因

  1. async void误用:HandleSubmit使用async void导致Blazor无法跟踪异步操作状态,引发UI更新异常
  2. 状态未全局重置:RegisterUser执行失败时未重置提交状态,重定向操作会触发组件重新渲染,私有变量被重置
  3. 原生表单提交行为:EditForm默认触发浏览器表单提交,导致页面刷新重置组件状态

修复步骤

1. 替换async void为async Task

Blazor需要通过Task跟踪异步操作,修改HandleSubmit:

private async Task HandleSubmit()
{
    if (stateService.IsSubmitting) return;
    
    stateService.IsSubmitting = true;
    try
    {
        await RegisterUser();
    }
    finally
    {
        // 无论成功失败都重置状态
        stateService.IsSubmitting = false;
        StateHasChanged();
    }
}

2. 阻止原生表单提交

在EditForm中添加原生事件阻止默认提交,避免页面刷新:

<EditForm Model="Input" asp-route-returnUrl="@ReturnUrl" OnValidSubmit="HandleSubmit" FormName="register" @onsubmit="e => e.PreventDefault()">

3. 修复单例状态共享问题

单例StateService会导致多用户共享状态,改用组件私有变量更合理:

private bool _isSubmitting = false;

private async Task HandleSubmit()
{
    if (_isSubmitting) return;
    
    _isSubmitting = true;
    try
    {
        await RegisterUser();
    }
    finally
    {
        _isSubmitting = false;
        StateHasChanged();
    }
}

按钮绑定改为:

<MudButton ButtonType="ButtonType.Submit" Disabled="@_isSubmitting">Register</MudButton>

4. 确保RegisterUser全分支重置状态

修改RegisterUser,在所有退出路径重置状态:

public async Task RegisterUser()
{
    try
    {
        // 原有注册逻辑...
    }
    finally
    {
        _isSubmitting = false;
        StateHasChanged();
    }
}

5. 测试项目的修复

对测试项目的RegisterUser做同样修改,并添加表单阻止默认提交:

private bool _isSubmitting = false;

public async Task RegisterUser(EditContext editContext)
{
    if (_isSubmitting) return;
    
    _isSubmitting = true;
    try
    {
        await Task.Delay(100);
        // 注册逻辑
    }
    finally
    {
        _isSubmitting = false;
        StateHasChanged();
    }
}
<EditForm Model="Input" asp-route-returnUrl="@ReturnUrl" method="post" OnValidSubmit="RegisterUser" FormName="register" @onsubmit="e => e.PreventDefault()">

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 11:59:55