This has probably been asked before, but I'm at a loss as to what's going on here: I have a Blazor page that calls a dialog component to edit, insert, delete a record in the database. After either Deleting or Inserting a record (not editing - no need), I want to simply refresh a grid (table) with the new results. So I get a return value from the component when it is closed that is either True or False : True = refresh parent/caller page with the new results, False = do nothing (when just changing a record). The problem is the UI is never updated (even if calling StateHasChanged()), and I understand that won't work as it is only a request to update the GUI, not a forced refresh. What I don't understand is exactly HOW to achieve this goal. MS BOL is confusing as heck and no actual working examples with a razor component. I know how to send data back to the caller - that's not the problem. In fact, that's working perfectly, but the Main page acts like it needs another event (though I want it to refresh automatically), but I'm not sure how to invoke a dummy event to get it to refresh properly. Here's the main/index page code:
@page "/"
@inject IConfiguration config
@inject DialogService dialog
@inject NotificationService notification
<PageTitle>Memo Master</PageTitle>
<RadzenButton Click="GetMemos" Text="Get Memos" ButtonStyle="ButtonStyle.Primary" ButtonType="ButtonType.Submit" />
<RadzenTextBox @ref="searchBox" Name="SearchPhrase" @bind-Value=@SearchString MaxLength="400" @oninput=@(args => SearchString = args.Value.ToString()) @onkeydown=@Enter /> @*searchString value continually updated for onkeydown to work*@
<RadzenButton Click="() => OpenMemo(0)" Text="New Memo" Icon="add_circle_outline" ButtonStyle="ButtonStyle.Secondary" />
<br />
<br />
@if (FoundMemos != null && busy == false)
{
<RadzenDataGrid Data="@FoundMemos" TItem="MemoSearch" AllowFiltering="true" AllowSorting="true" AllowColumnResize="true" AllowPaging="true" PageSize=20
FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" SelectionMode="DataGridSelectionMode.Single" @bind-Value="@SelectedMemos" RowClick="@OnRowClicked">
<Columns>
<RadzenDataGridColumn TItem="MemoSearch" Title="Index" Width="70px" Filterable="false" TextAlign="TextAlign.Left">
<Template Context="m">
<RadzenText TextStyle="TextStyle.Caption">@m.Idx.ToString()</RadzenText>
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="MemoSearch" Property="Title" Title="Title">
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="MemoSearch" Title="Modified" Width="140px" TextAlign="TextAlign.Right">
<Template Context="m">
<RadzenText TextStyle="TextStyle.Caption">@m.ModifiedOn.ToString("MM/dd/yyyy hh:mm tt")</RadzenText>
</Template>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
}
else
{
<DisplaySpinner />
}
<br />
<br />
<RadzenButton Click="Reset" Text="Reset" ButtonStyle="ButtonStyle.Secondary" />
@code {
List<MemoSearch> FoundMemos = new();
private string SearchString = "";
private string DBConnStr { get; set; } = "";
public DB dB = new();
IList<MemoSearch>? SelectedMemos;
RadzenTextBox searchBox = new();
private bool busy;
async Task OpenMemo(int Idx)
{
string DialogTitle = (Idx == 0) ? "Create New Memo" : $"Edit Memo {Idx.xToStr()}";
bool RefreshResults = await dialog.OpenAsync<MemoDetails>(DialogTitle, new Dictionary<string, object>() { { "Idx", Idx } });
if (RefreshResults)
{
await GetMemos(); //this method is called, but no page refresh
StateHasChanged(); //this isn't causing the page to refresh
}
}
protected override async Task OnInitializedAsync() => dB.DBConnStr = config.GetConnectionString("DBConnStr");
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await searchBox.Element.FocusAsync(); //NOTE: this is for Radzen "elements"
}
public async Task GetMemos()
{
busy = true;
FoundMemos = await dB.MemoSearchByPageFilterSortAsync(SearchString, PageSize: 9999);
busy = false;
}
public void Reset()
{
FoundMemos = new();
SearchString = "";
}
public async void Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter" || e.Key == "Enter")
{
await GetMemos();
StateHasChanged();
}
}
async Task OnRowClicked(Radzen.DataGridRowMouseEventArgs<MemoSearch> args)
{
if (args != null)
{
await OpenMemo(args.Data.Idx);
}
}
}
The Details razor component code:
@inject IConfiguration config
@inject DialogService dialog
@inject NotificationService notification
@if (memo != null)
{
<RadzenTemplateForm TItem="Memo" Data=@memo Submit=@OnSubmit>
<p>
<RadzenLabel Component="Title" Text="Title" />
<RadzenTextBox id="MemoTitle" Name="Title" @bind-Value=@memo.Title MaxLength="400" />
<RadzenRequiredValidator Component="Title" Text="Title is required!" />
</p>
<p>
<RadzenLabel Component="Body" Text="Memo" />
<RadzenTextArea id="MemoBody" Name="Body" @bind-Value=@memo.Body Rows="18" />
</p>
<p>
<RadzenLabel Component="Keywords" Text="Key Words" />
<RadzenTextBox id="MemoKeywords" Name="Keywords" @bind-Value=@memo.Keywords MaxLength="400" />
</p>
<RadzenButton ButtonType="ButtonType.Submit" ButtonStyle="ButtonStyle.Success" Icon="save" Text="Save" BusyText="Saving ..." IsBusy=@busy />
<RadzenButton ButtonType="ButtonType.Button" ButtonStyle="ButtonStyle.Danger" Icon="delete" Text="Delete" Click="@((args) => DeleteMemo(memo.Idx))" @onclick:stopPropagation="true"></RadzenButton>
<RadzenButton Text="Close" Click="() => dialog.Close(false)" ButtonStyle="ButtonStyle.Light" />
</RadzenTemplateForm>
}
@code {
[Parameter]
public int Idx { get; set; }
public DB dB = new();
Memo? memo;
bool busy;
protected override async void OnInitialized()
{
dB.DBConnStr = config.GetConnectionString("DBConnStr");
memo = (Idx == 0) ? new Memo() : await GetMemoByIdx(Idx);
await InvokeAsync(() => StateHasChanged()).ConfigureAwait(false); //IMPORTANT!!
}
public async Task<Memo> GetMemoByIdx(int Idx) => await dB.MemoSelectByIdxAsync(Idx);
async Task OnSubmit(Memo memo)
{
busy = true;
int Result;
bool RefreshResults = false;
if (memo.ModifiedOn == DateTime.MinValue) memo.ModifiedOn = DateTime.Now;
string NotificationDetailMessage = memo.Idx == 0 ? "New Memo has been created." : $"Memo {memo.Idx} has been saved.";
Result = await dB.MemoUpsertAsync(memo);
if (Result < -1)
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error Saving", Detail = "An error saving this record has occured!\n" + dB.LastErrorMsg, Duration = 4000 });
}
else
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Save Success", Detail = NotificationDetailMessage, Duration = 2000 });
}
busy = false;
RefreshResults = (memo.Idx == 0 && Result >= -1) ? true : false; //send the refresh message only if it's new memo AND it's successful
dialog.Close(RefreshResults);
}
async Task DeleteMemo(int Idx)
{
busy = true;
int Result;
bool RefreshResults = false;
var confirmResult = await dialog.Confirm("Are you sure?", "Confirm Memo Deletion");
if (confirmResult.HasValue && confirmResult.Value)
{
Result = await dB.MemoDeleteByIdxAsync(Idx);
if (Result < -1)
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error Deleting", Detail = "An error deleting this record has occured!\n" + dB.LastErrorMsg, Duration = 4000 });
}
else
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Deletion Success", Detail = $"Memo {Idx} has been deleted.", Duration = 2000 });
RefreshResults = true;
}
}
busy = false;
dialog.Close(RefreshResults);
}
}
As can be seen, a boolean value is returned (it works) from the dialog call inside the OpenMemo method, and I wish to refresh the UI if it's set to true, which I then call the GetMemos method and call StateHasChanged. I am able to step through the debugger and everything is working fine (FoundMemos has records, including the newly created one or minus a deleted one!), even the markup test is working, yet it doesn't display. Oddly, SOMETIMES when stepping through the code, the page refreshes! I can't figure out what is going on and why this is happening (and more importantly, how to fix it). I've tried the hacky Task.Delay(somenumber) in between the GetMemos call and the StateHasChanged, with no joy. What am I missing?
This is a Server Side Blazor app on DotNet7.0 (for in-house use)
************* New Code - Works except when searchbox has a value in it **
Index.razor:
@page "/"
@inject IConfiguration config
@inject DialogService dialog
@inject NotificationService notification
<PageTitle>Memo Master</PageTitle>
<RadzenButton Click="() => GetMemos()" Text="Get Memos" ButtonStyle="ButtonStyle.Primary" ButtonType="ButtonType.Submit" />
<RadzenTextBox @ref="searchBox" Name="SearchPhrase" @bind-Value=@SearchString MaxLength="400" @oninput=@(args => SearchString = args.Value.ToString()) @onkeydown=@Enter />
<RadzenButton Click="() => OpenMemo(0)" Text="New Memo" Icon="add_circle_outline" ButtonStyle="ButtonStyle.Secondary" />
<br />
<br />
@if (FoundMemos != null && !busy)
{
<RadzenDataGrid @ref=this.grid Data="@FoundMemos" TItem="MemoSearch" AllowFiltering="true" AllowSorting="true" AllowColumnResize="true" AllowPaging="true" PageSize=20
FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" SelectionMode="DataGridSelectionMode.Single" @bind-Value="@SelectedMemos" RowClick="@OnRowClicked">
<Columns>
<RadzenDataGridColumn TItem="MemoSearch" Title="Index" Width="70px" Filterable="false" TextAlign="TextAlign.Left">
<Template Context="m">
<RadzenText TextStyle="TextStyle.Caption">@m.Idx.ToString()</RadzenText>
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="MemoSearch" Property="Title" Title="Title">
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="MemoSearch" Title="Modified" Width="140px" TextAlign="TextAlign.Right">
<Template Context="m">
<RadzenText TextStyle="TextStyle.Caption">@m.ModifiedOn.ToString("MM/dd/yyyy hh:mm tt")</RadzenText>
</Template>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
}
else
{
<DisplaySpinner />
}
<br />
<br />
<RadzenButton Click="Reset" Text="Reset" ButtonStyle="ButtonStyle.Secondary" />
@code {
[Parameter]
public string? SearchString { get; set; }
List<MemoSearch> FoundMemos = new();
private string DBConnStr { get; set; } = "";
public DB dB = new();
IList<MemoSearch>? SelectedMemos;
RadzenTextBox? searchBox;
private bool busy;
private RadzenDataGrid<MemoSearch>? grid; //reference to grid, so forced reloading can happen - though it doesn't actually work
async Task OpenMemo(int Idx)
{
string DialogTitle = (Idx == 0) ? "Create New Memo" : $"Edit Memo {Idx.xToStr()}";
object? RefreshResults = await dialog.OpenAsync<MemoDetails>(DialogTitle, new Dictionary<string, object>() { { "Idx", Idx } });
RefreshResults = (RefreshResults == null) ? false : RefreshResults;
if (RefreshResults.xToBoo())
{
await GetMemos();
}
await ReturnFocus();
}
protected override async Task OnInitializedAsync()
{
dB.DBConnStr = config.GetConnectionString("DBConnStr");
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await ReturnFocus(); //NOTE: this is for Radzen "elements"
}
public async Task GetMemos()
{
busy = true;
FoundMemos = await dB.MemoSearchByPageFilterSortAsync(SearchString, PageSize: 9999);
busy = false;
}
public async Task Reset()
{
FoundMemos = new();
SearchString = "";
await ReturnFocus();
}
public async void Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter" || e.Key == "Enter")
{
await GetMemos();
StateHasChanged(); //need to call this here after keypress, lest you get a continual spinner
}
}
async Task OnRowClicked(Radzen.DataGridRowMouseEventArgs<MemoSearch> args)
{
if (args != null)
{
await OpenMemo(args.Data.Idx);
}
}
async Task ReturnFocus()
{
await searchBox.Element.FocusAsync();
}
}
MemoDetails.razor:
@inject IConfiguration config
@inject DialogService dialog
@inject NotificationService notification
@if (memo != null)
{
<RadzenTemplateForm TItem="Memo" Data=@memo Submit=@OnSubmit>
<p>
<RadzenLabel Component="Title" Text="Title" />
<RadzenTextBox id="MemoTitle" Name="Title" @bind-Value=@memo.Title MaxLength="400" />
<RadzenRequiredValidator Component="Title" Text="Title is required!" />
</p>
<p>
<RadzenLabel Component="Body" Text="Memo" />
<RadzenTextArea id="MemoBody" Name="Body" @bind-Value=@memo.Body Rows="18" />
</p>
<p>
<RadzenLabel Component="Keywords" Text="Key Words" />
<RadzenTextBox id="MemoKeywords" Name="Keywords" @bind-Value=@memo.Keywords MaxLength="400" />
</p>
<RadzenButton ButtonType="ButtonType.Submit" ButtonStyle="ButtonStyle.Success" Icon="save" Text="Save" BusyText="Saving ..." IsBusy=@busy />
@if (Idx > 0)
{
<RadzenButton ButtonType="ButtonType.Button" ButtonStyle="ButtonStyle.Danger" Icon="delete" Text="Delete" Click="@((args) => DeleteMemo(memo.Idx))" @onclick:stopPropagation="true"></RadzenButton>
}
<RadzenButton Text="Close" Click="() => dialog.Close(false)" ButtonStyle="ButtonStyle.Light" />
</RadzenTemplateForm>
}
@code {
[Parameter]
public int Idx { get; set; } = 0;
public DB dB = new();
Memo? memo;
bool busy;
protected override async void OnInitialized()
{
dB.DBConnStr = config.GetConnectionString("DBConnStr");
memo = (Idx == 0) ? new Memo() : await GetMemoByIdx(Idx);
await InvokeAsync(() => StateHasChanged()).ConfigureAwait(false); //IMPORTANT!!
}
public async Task<Memo> GetMemoByIdx(int Idx) => await dB.MemoSelectByIdxAsync(Idx);
async Task OnSubmit(Memo memo)
{
int Result;
bool RefreshResults = false;
if (memo.ModifiedOn == DateTime.MinValue) memo.ModifiedOn = DateTime.Now;
string NotificationDetailMessage = memo.Idx == 0 ? "New Memo has been created." : $"Memo {memo.Idx} has been saved.";
busy = true;
Result = await dB.MemoUpsertAsync(memo);
if (Result < -1)
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error Saving", Detail = "An error saving this record has occured!\n" + dB.LastErrorMsg, Duration = 4000 });
}
else
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Save Success", Detail = NotificationDetailMessage, Duration = 2000 });
RefreshResults = true;
}
busy = false;
dialog.Close(RefreshResults);
}
async Task DeleteMemo(int Idx)
{
int Result;
bool RefreshResults = false;
var confirmResult = await dialog.Confirm("Are you sure?", "Confirm Memo Deletion");
if (confirmResult.HasValue && confirmResult.Value)
{
busy = true;
Result = await dB.MemoDeleteByIdxAsync(Idx);
if (Result < -1)
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error Deleting", Detail = "An error deleting this record has occured!\n" + dB.LastErrorMsg, Duration = 4000 });
}
else
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Deletion Success", Detail = $"Memo {Idx} has been deleted.", Duration = 2000 });
RefreshResults = true;
}
}
busy = false;
dialog.Close(RefreshResults);
}
}