Blazor Server如何为注入的DialogService绑定页面专属事件处理程序
你遇到的多页面邮件同时发送的核心原因:Radzen默认注册的DialogService是Scoped生命周期,Blazor Server中Scoped服务的生命周期对应用户的整个连接(电路),而非单个页面。你在每个页面的OnInitialized中都给同一个DialogService实例的OnClose事件绑定了当前页面的Close处理方法,只要页面没有正确解绑该事件,处理方法就会一直留在事件的委托调用列表中,每次OnClose触发时所有残留的处理方法都会执行,导致所有已打开过的页面都执行了SendEmail逻辑。
Radzen DialogService需要和根组件中挂载的DialogHost实例绑定才能正常弹出模态框,注册为Transient后,每个页面注入的都是全新的DialogService实例,和全局DialogHost不关联,调用OpenAsync根本无法弹出模态框,完全不能解决你的问题。
Radzen的DialogService.OpenAsync本身会返回模态框关闭时传入的结果,你完全不需要订阅全局的OnClose事件,直接等待返回结果判断即可,从根源上避免多页面事件冲突。
修改后的代码如下:
首先删除原有OnInitialized中的事件绑定逻辑、单独的Close处理方法,调整Submit和SubmitModal的逻辑:
async Task Submit() { // 直接等待模态框的返回结果 var confirmResult = await SubmitModal(); if (confirmResult == true) { await JS.InvokeAsync<string>("DisableDialogSubmit", "nhDialogSubmit"); SendEmail(); // 只会执行当前页面的邮件发送逻辑 } } void OnInvalidSubmit() { } // 修改返回值为bool?,直接接收模态框关闭时传入的结果 async Task<bool?> SubmitModal() => await DialogService.OpenAsync<bool?>("Confirm Submission", (ds => @<div style="top: 25%"> <p Style="margin-bottom: 1rem">Submit Form?</p> <div class="row"> <div class="col-md-12"> <RadzenButton id="nhDialogSubmit" Text="Submit" Disabled="false" Click="() => ds.Close(true)" Style="margin-bottom: 10px; width: 150px" /> <RadzenButton Text="Cancel" Click="() => ds.Close(false)" ButtonStyle="ButtonStyle.Secondary" Style="margin-bottom: 10px; width: 150px" /> </div> </div> </div>), new DialogOptions() { Width = "350px", Top = "35%", Left = "25%" });
如果你有特殊场景必须订阅DialogService的全局事件,必须保证事件绑定和解绑成对出现,除了提交时解绑外,还需要在页面销毁时解绑,避免页面导航后事件残留:
- 让页面组件实现
IDisposable接口 - 在Dispose方法中解绑事件:
public void Dispose() { DialogService.OnOpen -= Open; DialogService.OnClose -= Close; }
你当前每个页面复制重复的模态框代码属于冗余实现,你可以将通用确认模态框封装为独立组件或者DialogService的扩展方法,只需要传入确认后的执行逻辑即可,不需要每个页面都重复编写模态框渲染代码。
- 掌握Blazor不同生命周期的区别,尤其是Blazor Server中Scoped生命周期对应电路而非单个页面的特性
- 学习.NET中委托事件的正确绑定、解绑方式,避免事件订阅导致的内存泄漏和逻辑异常
- 了解Blazor组件封装、服务注册的最佳实践,减少重复代码
内容的提问来源于stack exchange,提问作者Ubnik

