Blazor WASM触发SignalR事件后UI无法刷新问题求助
Blazor WASM SignalR事件触发后UI无法刷新问题解决
问题概述
基于.NET 7的Blazor WASM应用,通过SignalR客户端接收OnProfitCreated事件后,已成功更新_profitList数据,但调用await InvokeAsync(StateHasChanged)后,MudTable UI仍未刷新。日志显示数据已更新,但界面无变化。
相关代码
SignalR服务事件处理代码
_hubConnection.On("OnProfitCreated", OnProfitCreated); public event Func<Task>? ProfitCreated; private async Task OnProfitCreated() { if(ProfitCreated is not null) await ProfitCreated.Invoke(); }
组件核心逻辑代码
private PagedList<Profit> _profitList = null!; protected override async Task OnInitializedAsync() { SignalRService.ProfitCreated += UpdateProfits; _profitList = await SignalRService.GetProfitsAsync(_parameters); } private async Task UpdateProfits() { Console.WriteLine($"UpdateProfits start, _profitList count:{_profitList.Items.Count}"); _profitList = await SignalRService.GetProfitsAsync(_parameters); Console.WriteLine($"New profit list, _profitList count:{_profitList.Items.Count}"); await InvokeAsync(StateHasChanged); Console.WriteLine($"UpdateProfits exits, _profitList count:{_profitList.Items.Count}"); }
完整组件代码
Razor部分
@page "/profits" @implements IDisposable <PageTitle>Profits</PageTitle> <MudTable ServerData="@(ServerReload)" T="Profit" Dense="true" Hover="true" Loading="@_processing" RowsPerPage="15" @ref="_table"> <ToolBarContent> <MudTextField T="string" ValueChanged="@(s=>OnSearch(s))" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField> </ToolBarContent> <HeaderContent> <MudTh><MudTableSortLabel SortLabel="@nameof(Profit.Id)" T="Profit">Rd.</MudTableSortLabel></MudTh> <MudTh><MudTableSortLabel SortLabel="@nameof(Profit.Amount)" T="Profit">Amount</MudTableSortLabel></MudTh> <MudTh><MudTableSortLabel SortLabel="@nameof(Profit.SellingOrder.AssetPair.Name)" T="Profit">Pair</MudTableSortLabel></MudTh> <MudTh><MudTableSortLabel SortLabel="@nameof(Profit.CreatedDate)" T="Profit">Created Date</MudTableSortLabel></MudTh> <MudTh></MudTh> </HeaderContent> <RowTemplate> <MudTd DataLabel="Rd.">@context.Id</MudTd> <MudTd DataLabel="Amount"> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.QuoteAsset" /> @context.Amount </MudText> </MudTd> <MudTd DataLabel="Name"> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.BaseAsset"/> <CoinImage Symbol="@context.SellingOrder.AssetPair.QuoteAsset" /> @context.SellingOrder.AssetPair.Name </MudText> </MudTd> <MudTd DataLabel="CreatedDate"> <MudText> @context.CreatedDate.ToString("MM/dd/yyyy HH:mm:ss") </MudText> </MudTd> <MudTd><MudButton Variant="Variant.Filled" OnClick="()=>OpenChildData(context.Id)">Show</MudButton></MudTd> </RowTemplate> <!-- 子数据内容 --> <ChildRowContent> <MudTr> <td colspan="5"> @if (context.Id == _selectedChild) { <MudSimpleTable Style="overflow-x: auto;width:100%"> <thead> <tr> <th>Side</th> <th>Original Quantity</th> <th>Executed Quantity</th> <th>Price</th> <th>Price paid</th> <th>Type</th> <th>Created Date</th> <th>Update Date</th> <th>Status</th> </tr> </thead> <tbody> @if (context.SellingOrder.BuyOrder is not null) { <tr style='@(context.SellingOrder.BuyOrder.IsActive ? "color:#F8BBD0" : "")'> <td> <MudText> @context.SellingOrder.BuyOrder.Side </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.BaseAsset"></CoinImage> @context.SellingOrder.BuyOrder.OrigQty </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.BaseAsset"></CoinImage> @context.SellingOrder.BuyOrder.ExecutedQty </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.QuoteAsset"></CoinImage> @context.SellingOrder.BuyOrder.Price.ToString("N2") </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.QuoteAsset"></CoinImage> @context.SellingOrder.BuyOrder.PricePaid.ToString("N2") </MudText> </td> <td> <MudText> @context.SellingOrder.BuyOrder.Type </MudText> </td> <td> <MudText> @context.SellingOrder.BuyOrder.CreatedDate.ToString("MM/dd/yyyy HH:mm:ss") </MudText> </td> <td> @if (context.SellingOrder.BuyOrder.UpdateDate.HasValue) { <MudText>@context.SellingOrder.BuyOrder.UpdateDate.Value.ToString("MM/dd/yyyy HH:mm:ss")</MudText> } else { <MudIcon Color="Color.Error" Icon="@Icons.Material.Filled.Clear"></MudIcon> } </td> <td> <MudText> @context.SellingOrder.BuyOrder.Status </MudText> </td> </tr> } <tr style='@(context.SellingOrder.IsActive ? "color:#F8BBD0" : "")'> <td> <MudText>@context.SellingOrder.Side</MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.BaseAsset"></CoinImage> @context.SellingOrder.OrigQty </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.BaseAsset"></CoinImage> @context.SellingOrder.ExecutedQty </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.QuoteAsset"></CoinImage> @context.SellingOrder.Price.ToString("N2") </MudText> </td> <td> <MudText> <CoinImage Symbol="@context.SellingOrder.AssetPair.QuoteAsset"></CoinImage> @context.SellingOrder.PricePaid.ToString("N2") </MudText> </td> <td> <MudText>@context.SellingOrder.Type</MudText> </td> <td> <MudText>@context.SellingOrder.CreatedDate.ToString("MM/dd/yyyy HH:mm:ss")</MudText> </td> <td> @if (context.SellingOrder.UpdateDate.HasValue) { <MudText> @context.SellingOrder.UpdateDate.Value.ToString("MM/dd/yyyy HH:mm:ss")</MudText> } else { <MudIcon Color="Color.Error" Icon="@Icons.Material.Filled.Clear"></MudIcon> } </td> <td> <MudText>@context.SellingOrder.Status</MudText> </td> </tr> </tbody> </MudSimpleTable> } </td> </MudTr> </ChildRowContent> <NoRecordsContent> <MudText>No matching records found</MudText> </NoRecordsContent> <LoadingContent> <MudText>Loading...</MudText> </LoadingContent> <PagerContent> <MudTablePager PageSizeOptions="new int [] { 15, 30, 50}" /> </PagerContent> </MudTable>
@Code部分
@code { [Inject] SignalRService SignalRService { get; set; } = null!; [Inject] NavigationManager NavigationManager { get; set; } = null!; private int? _selectedChild = null!; private bool _processing = false; private PagedList<Profit> _profitList = null!; private ProfitParameters _parameters { get; set; } = new() { PageNumber = 1, PageSize = 15, Direction = SortByDirection.Descending, OrderBy = nameof(Profit.CreatedDate) }; private MudTable<Profit> _table; private int totalItems; private string? searchString = null; protected override async Task OnInitializedAsync() { SignalRService.ProfitCreated += UpdateProfits; _profitList = await SignalRService.GetProfitsAsync(_parameters); } private async Task UpdateProfits() { Console.WriteLine($"UpdateProfits start, _profitList count:{_profitList.Items.Count}"); _profitList = await SignalRService.GetProfitsAsync(_parameters); Console.WriteLine($"New profit list, _profitList count:{_profitList.Items.Count}"); await InvokeAsync(StateHasChanged); Console.WriteLine($"UpdateProfits exits, _profitList count:{_profitList.Items.Count}"); } private async Task<TableData<Profit>> ServerReload(TableState state) { _parameters.OrderBy = state.SortLabel; _parameters.PageSize = state.PageSize; _parameters.PageNumber = state.Page; _parameters.Direction = state.SortDirection == SortDirection.Descending ? SortByDirection.Descending : SortByDirection.Ascending; _profitList = await SignalRService.GetProfitsAsync(_parameters); return new TableData<Profit>() { TotalItems = _profitList.MetaData.TotalCount, Items = _profitList.Items }; } private void OnSearch(string text) { _parameters.Filter.Currency = text; _table.ReloadServerData(); } private void OpenChildData(int profitId) { switch (_selectedChild) { case null: _selectedChild = profitId; break; case var selectedId when selectedId == profitId: _selectedChild = null; break; default: _selectedChild = profitId; break; } } public void Dispose() { SignalRService.ProfitCreated -= UpdateProfits; } }
日志输出
UpdateProfits start, _profitList count:0 New profit list, _profitList count:0 UpdateProfits exits, _profitList count:0 New profit list, _profitList count:0 UpdateProfits exits, _profitList count:0 New profit list, _profitList count:1 UpdateProfits exits, _profitList count:1
问题原因
你使用了MudTable的ServerData模式,这种模式下表格的数据完全由ServerReload回调方法驱动,直接更新组件的_profitList字段不会触发表格的UI刷新。表格不会主动监听_profitList的变化,必须通过调用表格实例的刷新方法来触发ServerReload重新获取数据并渲染。
解决方案
修改UpdateProfits方法,在获取最新数据后调用MudTable的ReloadServerData()方法,确保表格重新加载数据并刷新UI:
private async Task UpdateProfits() { Console.WriteLine($"UpdateProfits start, _profitList count:{_profitList.Items.Count}"); // 在UI上下文执行表格刷新操作 await InvokeAsync(() => _table.ReloadServerData()); // 刷新后同步本地_profitList _profitList = await SignalRService.GetProfitsAsync(_parameters); Console.WriteLine($"UpdateProfits exits, _profitList count:{_profitList.Items.Count}"); }
或者更高效的方式,直接调用ServerReload获取数据并更新表格:
private async Task UpdateProfits() { Console.WriteLine($"UpdateProfits start, _profitList count:{_profitList.Items.Count}"); await InvokeAsync(async () => { // 使用表格当前状态重新获取数据 var tableData = await ServerReload(_table.CurrentState); // 更新本地_profitList _profitList = new PagedList<Profit>(tableData.Items, _parameters.PageNumber, _parameters.PageSize, tableData.TotalItems); // 通知表格更新数据 _table.SetServerData(tableData); }); Console.WriteLine($"UpdateProfits exits, _profitList count:{_profitList.Items.Count}"); }
额外注意事项
- 确保
_table引用已通过@ref="_table"正确绑定 - SignalR事件回调在非UI线程触发时,必须使用
InvokeAsync切换到UI线程执行组件操作 - 分页、排序等状态会被
ServerReload自动处理,无需手动调整参数
内容的提问来源于stack exchange,提问作者exzzy
相关产品推荐
相关产品推荐

