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

.NET 8 Blazor文档库操作后页面事件失效问题求助

问题:Blazor上传/删除操作后所有事件停止响应

我正在基于.NET 8开发Blazor文档库项目,支持文件拖拽上传、下载及删除功能。操作前所有功能正常,上传或删除后页面和组件能正确更新,但所有Blazor事件完全停止响应(比如点击页面元素无任何反应)。浏览器控制台无错误输出,调试模式下也未抛出异常,删除操作未使用JS,因此问题似乎与JsInterop无关。


核心代码

BaseDocumentVaultPage.cs

public class BaseDocumentVaultPage : AuthedBasePage
{
    [Inject]
    public IStateContainer<DocumentsMetaDataVm> StateContainer { get; set; }

    [Inject]
    public IStateContainer<PopoverOptions> ProgressStateContainer { get; set; }

    [Inject]
    public ICurrentUserService CurrentUserService { get; set; }

    [Inject]
    public IDocumentVaultDataBroker DocumentVaultDataBroker { get; set; }

    public async Task<DocumentsMetaDataVm> LoadDataFromApiAsync()
    {
        //Omitted, this appears to be working correctly
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await StateContainer.GetOrInitializeValueAsync(LoadDataFromApiAsync);
        }
    }
}

AdvisorDocumentVaultPage.razor

@page "/advisor/document-vault"
@layout BasePageLayout
@inherits BaseDocumentVaultPage
@rendermode InteractiveAuto

<div class="grid grid-cols-12 gap-6">

    <FileUploadComponent
        FileOwner="@FileOwnerEnum.Advisor"
        DocumentsMetaData="@DocumentsMetaData"
        OnUpload="@FilesUploadedAsync" />

</div>

<div class="grid grid-cols-12 gap-6">

    <FileListComponent
        CanEdit="true"
        FileOwner="@FileOwnerEnum.Advisor"
        DocumentsMetaData="@(DocumentsMetaData?.AdvisorDocuments ?? new List<DocumentMetaDataVm>())"
        Title="Shared by Me"
        OnFileDeleted="@OnDeleteAsync"
        OnFileRetrieval="@OnRetrievalAsync" />

</div>

AdvisorDocumentVaultPage.razor.cs

public partial class AdvisorDocumentVaultPage
{
    public DocumentsMetaDataVm DocumentsMetaData { get; set; } = new();

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        StateContainer.OnStateChange += StateContainer_OnStateChange;
        StateContainer.OnInitialized += StateContainer_OnInitialized;

        await SetPageDetailsAsync("Document Vault", new Dictionary<string, string?>()
        {
            {"Advisor", "/advisor"}
        });

        DocumentsMetaData = StateContainer.CurrentValue ?? new DocumentsMetaDataVm()
        {
            AdvisorDocuments = new List<DocumentMetaDataVm>(),
            ClientDocuments = new List<DocumentMetaDataVm>()
        };
    }

    public async Task StateContainer_OnInitialized(DocumentsMetaDataVm? arg)
    {
        DocumentsMetaData = StateContainer.CurrentValue ?? new DocumentsMetaDataVm()
        {
            AdvisorDocuments = new List<DocumentMetaDataVm>(),
            ClientDocuments = new List<DocumentMetaDataVm>()
        };

        await InvokeAsync(StateHasChanged);
    }

    public async Task StateContainer_OnStateChange(DocumentsMetaDataVm? arg)
    {
        DocumentsMetaData = StateContainer.CurrentValue ?? new DocumentsMetaDataVm()
        {
            AdvisorDocuments = new List<DocumentMetaDataVm>(),
            ClientDocuments = new List<DocumentMetaDataVm>()
        };

        ProgressStateContainer.UpdateValue(new PopoverOptions(false));

        await InvokeAsync(StateHasChanged);
    }

    public async Task FilesUploadedAsync(IReadOnlyList<IBrowserFile> files)
    {
        try
        {
            ProgressStateContainer.UpdateValue(new PopoverOptions(true));

            await DocumentVaultDataBroker.SaveDocumentAsync(UserContext.ClientId.GetValueOrDefault(),
                UserContext.ToAdvisorRequest(), FileOwnerEnum.Advisor, files);
        }
        catch (Exception ex)
        {
            await DispatchExceptionAsync(ex);
        }
        finally
        {
            ProgressStateContainer.UpdateValue(new PopoverOptions(false));
            await base.OnInitializedAsync();
        }
    }
}

FileUploadComponent.razor

<div class="col-span-12 xxl:col-span-6" >
    <div class="box">
        <div class="box-header">
            <h5 class="box-title">File Upload</h5>
        </div>
        <div class="box-body" >
            <div @ref="dropZoneElement" class="document-vault-drop-zone">
                <div id="drag-and-drop-div">
                    Drag &amp; Drop or
                    <InputFile OnChange="@OnChange" multiple @ref="inputFile" id="document-vault-file-input"/>
                </div>
            </div>        
        </div>
    </div>
</div>

FileUploadComponent.razor.cs

public partial class FileUploadComponent
{    
    [Inject]
    public IDocumentVaultJsInteropService JsInteropService { get; set; }

    [Parameter]
    public FileOwnerEnum? FileOwner { get; set; }

    [Parameter]
    public DocumentsMetaDataVm? DocumentsMetaData { get; set; }

    [Parameter]
    public EventCallback<IReadOnlyList<IBrowserFile>> OnUpload { get; set; }

    private const int MaxAllowedFiles = 5;
    public ElementReference? dropZoneElement;
    public InputFile? inputFile;

    public async Task OnChange(InputFileChangeEventArgs args)
    {
        if (args.FileCount > MaxAllowedFiles)
        {
            //todo: add a toast error
        }

        await OnUpload.InvokeAsync(args.GetMultipleFiles(MaxAllowedFiles));
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            if (inputFile != null && dropZoneElement != null)
            {
                await JsInteropService.InitializeFileDropZone(dropZoneElement, inputFile.Element);
            }
        }
    }
}

IDocumentVaultDataBroker实现

public class DocumentVaultDataBroker(IMediator mediator, IMapper mapper, IStateContainer<DocumentsMetaDataVm> stateContainer, INotificationHandler notificationHandler) : IDocumentVaultDataBroker
{
    public async Task SaveDocumentAsync(long clientId, AdvisorRequestContext context, FileOwnerEnum fileOwner, IReadOnlyList<IBrowserFile> documents,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var vms = new List<DocumentMetaDataVm>();

            foreach (var file in documents)
            {
                await using var stream =
                    file.OpenReadStream(maxAllowedSize: 5242880, cancellationToken: cancellationToken);
                using var ms = new MemoryStream();
                await stream.CopyToAsync(ms, cancellationToken);

                var documentBytes = ms.ToArray();

                //Note, handler calls API
                var result = await mediator.Send(new SaveDocumentCommand()
                {
                    ClientId = clientId,
                    RequestContext = context,
                    FileOwner = fileOwner,
                    FileNameWithExtension = file.Name,
                    Document = documentBytes
                }, cancellationToken);

                vms.Add(mapper.Map<DocumentMetaDataVm>(result));
            }

            var documentsMetaData = stateContainer.CurrentValue.Clone();

            if (documentsMetaData == null)
            {
                documentsMetaData = new DocumentsMetaDataVm()
                {
                    AdvisorDocuments = new List<DocumentMetaDataVm>(),
                    ClientDocuments = new List<DocumentMetaDataVm>()
                };
            }

            documentsMetaData.TotalBytesUsed.Bytes += vms.Sum(vm => vm.FileSizeInBytes ?? 0);

            if (fileOwner == FileOwnerEnum.Advisor)
            {
                documentsMetaData.AdvisorDocuments.AddRange(vms);
            }
            else
            {
                documentsMetaData.ClientDocuments.AddRange(vms);
            }

            var message = documents.Count > 1
                ? $"{documents.Count} documents successfully uploaded."
                : "Document successfully uploaded.";

            stateContainer.UpdateValue(documentsMetaData, new SuccessNotificationDto(message));
        }
        catch (Exception e)
        {
            await notificationHandler.HandleExceptionAsync("Error while uploading document", e);
        }
    }
}

已尝试的排查措施

  • 将JsInterop改为作用域服务,确保模块仅加载一次
  • 更新状态容器前克隆VM对象
  • 改用页面回调替代组件中注入DataBroker
  • 与其他开发者一同审查代码,仍未找到问题原因

排查思路

  1. 检查事件重复订阅问题:FilesUploadedAsync的finally块中调用了base.OnInitializedAsync(),这会重复订阅StateContainer的OnStateChange和OnInitialized事件。多次订阅会导致状态变更时重复执行逻辑,可能引发UI线程死锁或组件状态混乱。建议移除该调用,改为直接调用StateContainer.GetOrInitializeValueAsync(LoadDataFromApiAsync)刷新数据。

  2. 验证StateHasChanged调用逻辑:确认StateContainer的状态变更事件是否在UI线程触发。如果事件在非UI线程执行,即使调用InvokeAsync(StateHasChanged)也可能导致UI更新异常。可以检查StateContainer的UpdateValue实现,确保事件触发在UI线程。

  3. 排查组件生命周期冲突:页面使用@rendermode InteractiveAuto,上传后调用base.OnInitializedAsync()可能触发组件重新初始化,导致原有组件实例被替换、事件绑定失效。尝试在上传完成后直接刷新状态容器数据,而非重新初始化页面。

  4. 检查StateContainer线程安全性:如果StateContainer在多线程环境下被修改,可能导致UI状态不一致。检查UpdateValue方法是否有线程安全处理(如锁机制),确保状态变更操作在单线程执行。

  5. 测试移除ProgressStateContainer更新:暂时注释掉ProgressStateContainer.UpdateValue调用,确认是否是进度弹窗的状态更新打乱了Blazor的UI渲染队列,导致事件失效。

  6. 重置InputFile组件状态:上传完成后,InputFile组件未重置可能导致DOM元素状态异常。可以在FilesUploadedAsync完成后,通过JsInterop重置InputFile的value,或调用inputFile?.Reset()(若支持)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 20:14:57