So, I'm using Blazor WASM with the latest .NET 7 and have a SignalR client that has registered a handler that raises an event.
_hubConnection.On("OnProfitCreated", OnProfitCreated);
public event Func<Task>? ProfitCreated;
private async Task OnProfitCreated()
{
if(ProfitCreated is not null)
await ProfitCreated.Invoke();
}
And here is the Blazor component:
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}");
}
And here is the result of log:
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
I tried using
await InvokeAsync(StateHasChanged);
to synchronize contexts because that event is raised on a different thread, but the UI still isn't refreshing.
Edit: Added code for whole component
@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>
<!-- Add the following code for the child data -->
<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 {
[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;
}
}
If I'm reading this wall of code correctly, you have your MudTable wired up like this:
<MudTable ServerData="@(ServerReload)" T="Profit" Dense="true" Hover="true" Loading="@_processing" RowsPerPage="15" @ref="_table">
i.e. to a delegate that provides the data to display.
Note the following from the documentation:
Set ServerData to load data from the backend that is filtered, sorted and paginated. Table will call this async function whenever the user navigates the pager or changes sorting by clicking on the sort header icons.
Those are internal events. The table isn't updating because there's nothing triggering a data set update when the ProfitCreated
event occurs.
I'm no MudBlazor expert, but what you probably need to do is go back to linking the table into an external list using Items
, which when it changes should trigger a UI update in the table.
Hopefully someone from MudBlazor will see this and provide better guidance if I'm wrong.