当 API 调用完成时,如何从 .RAZOR 主页面中的所有子组件触发/刷新我的主 .RAZOR 页面?
Posted
技术标签:
【中文标题】当 API 调用完成时,如何从 .RAZOR 主页面中的所有子组件触发/刷新我的主 .RAZOR 页面?【英文标题】:How can I trigger/refresh my main .RAZOR page from all of its sub-components within that main .RAZOR page when an API call is complete? 【发布时间】:2021-12-02 01:57:18 【问题描述】:我正在开发一款可让用户搜索我们的数据库的应用。当用户输入搜索词时,应用程序会点击 API 端点并返回数据。然后我显示数据。
当 API 返回数据时,我有一个作用域服务:
services.AddScoped<AppState>();
这样可以保留每个返回的数据集,以便在应用程序的所有组件中使用。
SearchResults.razor
页面一加载,它就会从我的作用域服务中获取结果,然后绘制页面的其余部分。
我需要一个“加载”微调器来代替数据,直到 API 返回数据,这可能需要很长时间,具体取决于搜索的数据量。
我的问题是,我无法弄清楚使用什么作为真/假“触发器”来了解是否显示数据或加载微调器,或者一旦 API 向我发送数据后如何刷新页面.
我下面的内容仅适用于第一次初始搜索(来自我的Index.razor
页面),但不适用于任何包含的“过滤器”组件。
SearchResults.razor:
@page "/searchresults"
@layout PageTopComponents
<Header.razor></Header.razor>
<LeftMenu.razor>
<FilterRazorComponent01.razor></FilterRazorComponent01.razor>
<FilterRazorComponent02.razor></FilterRazorComponent02.razor>
<FilterRazorComponent03.razor></FilterRazorComponent03.razor>
<FilterRazorComponent04.razor></FilterRazorComponent04.razor>
</LeftMenu.razor>
<MainContentComponent.razor>
// CONTENT HERE SHOULD BE VISIBLE WHEN DATA HAS ARRIVED, OTHERWISE IT SHOULD SHOW A "WAITING" SPINNER
@if(API_Data_Received != null && API_Data_Received.count > 0)
foreach()
// API Retrieved Data Here
else
// Loading Spinner
<ContinueSearch.razor></ContinueSearch.razor>
<Paginator.razor @ref="PaginatorComponentReference">
<ChildContent>
// THIS IS WHERE I DISPLAY ALL SEARCH DATA ...
// CONTAINS: public Paginator PaginatorComponentReference;
</ChildContent>
</Paginator.razor>
</MainContentComponent.razor>
@code
// code here ...
public async Task GetQueryStringValues()
Uri uri = navigationManager.ToAbsoluteUri(System.Net.WebUtility.UrlDecode(navigationManager.Uri));
Dictionary<string, StringValues> queryStrings = QueryHelpers.ParseQuery(uri.Query);
Paginator.razor:
<div> [ << ] [ < ] NAVIGATION [ > ] [ >> ] </div>
@ChildContent // Is "ChildContent" in SearchResults.razor
<div> [ << ] [ < ] NAVIGATION [ > ] [ >> ] </div>
我包含的大部分 .RAZOR 组件都会进行某种“过滤”并使用以下内容:
String href = "/searchresults" + // other parameters here ...
NavigationManager.NavigateTo(href);
意思是,每当我“过滤”时,我总是点击SearchResults.razor
页面。
我相信我已经在所有可覆盖的方法中尝试了await InvokeAsync(StateHasChanged);
的某种组合:
-
OnInitialized()
OnInitializedAsync()
OnParametersSet()
OnParametersSetAsync()
OnAfterRender()
OnAfterRenderAsync()
但是从我在Index.razor
的表单条目中第一次加载SearchResults.razor
之后似乎没有任何效果。
我需要做什么才能让它工作? 似乎很简单,但我就是想不通。
【问题讨论】:
@BrianParker 非常感谢您的回复;然而,分页只是其中的一小部分。我还使用了其他过滤组件。 【参考方案1】:答案显示了如何更新 Blazor WeatherForecast 应用程序以演示状态/通知模式以及如何在组件中使用它。我使用了 Weather Forecast 应用程序,因为您的问题中没有足够的细节来使用您的代码作为答案的基础,而且 Weather Forecast 应用程序提供了一个很好的模板来构建。
起点是标准 Blazor Server 模板项目。我的叫***.Answers
添加一个Loading.razor
组件。这将检测加载状态并在记录加载时显示一个旋转器。
@if (this.IsLoaded)
@this.ChildContent
else
<div class="loader"></div>
@code
[Parameter] public RenderFragment ChildContent get; set;
[Parameter] public bool IsLoaded get; set;
添加组件 CSS 文件 - Loading.razor.css
- 以格式化旋转器:
.page-loader
position: absolute;
left: 50%;
top: 50%;
z-index: 1;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
.loader
border: 16px solid #f3f3f3;
/* Light grey */
border-top: 16px solid #3498db;
/* Blue */
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
margin-left: auto;
margin-right: auto;
@-webkit-keyframes spin
0%
-webkit-transform: rotate(0deg);
100%
-webkit-transform: rotate(360deg);
@keyframes spin
0%
transform: rotate(0deg);
100%
transform: rotate(360deg);
我将原始服务拆分为单独的数据和视图服务(良好的设计实践)。
更新WeatherForecastService
。它现在是数据服务,它需要做的就是提供数据。在一个真实的应用程序中,这将与数据代理交互以获取真实数据。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ***.Answers.Data
public class WeatherForecastService
private static readonly string[] Summaries = new[]
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
;
private List<WeatherForecast> recordsShort;
private List<WeatherForecast> recordsLong;
public WeatherForecastService()
recordsShort = GetForecastsShort;
recordsLong = GetForecastsLong;
public async Task<List<WeatherForecast>> GetForecastsAsync(bool islong = false)
await Task.Delay(3000);
return islong ? this.recordsLong : this.recordsShort;
public List<WeatherForecast> GetForecastsShort
get
var rng = new Random();
return Enumerable.Range(1, 3).Select(index => new WeatherForecast
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
).ToList();
public List<WeatherForecast> GetForecastsLong
get
var rng = new Random();
return Enumerable.Range(1, 6).Select(index => new WeatherForecast
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
).ToList();
将新的WeatherForecastViewService
类添加到Data 文件夹。这是我们的视图服务。它保存我们的数据,是 UI 使用的服务。它从数据服务中获取数据并公开Records
列表和ListChanged
事件,该事件在列表更改时触发。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ***.Answers.Data
public class WeatherForecastViewService
public List<WeatherForecast> Records get; set;
private WeatherForecastService weatherForecastService;
public WeatherForecastViewService(WeatherForecastService weatherForecastService)
this.weatherForecastService = weatherForecastService;
public async Task GetForecastsAsync(bool islong = false)
this.Records = null;
this.NotifyListChanged(this.Records, EventArgs.Empty);
this.Records = await weatherForecastService.GetForecastsAsync(islong);
this.NotifyListChanged(this.Records, EventArgs.Empty);
public event EventHandler<EventArgs> ListChanged;
public void NotifyListChanged(object sender, EventArgs e)
=> ListChanged?.Invoke(sender, e);
添加一个新组件 - WeatherForecastList.razor
。这是来自Fetchdata
的胆量。它:
-
使用新的
Loading
组件。
使用新的 WeatherForecastViewService。
直接使用来自WeatherForecastViewService
的列表。它没有自己的副本 - 所有组件都使用相同的列表。
在触发 evwnt 时连接到视图服务 ListChanged
事件并调用 StateHasChanged
。
@implements IDisposable
@using ***.Answers.Data
<h1>Weather forecast</h1>
<Loading IsLoaded="this.isLoaded" >
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in viewService.Records)
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
</tbody>
</table>
</Loading>
@code
[Inject] private WeatherForecastViewService viewService get; set;
private bool isLoaded => viewService.Records is not null;
protected override async Task OnInitializedAsync()
await GetForecastsAsync();
this.viewService.ListChanged += this.OnListChanged;
private async Task GetForecastsAsync()
=> await viewService.GetForecastsAsync();
private void OnListChanged(object sender, EventArgs e)
=> this.InvokeAsync(this.StateHasChanged);
public void Dispose()
this.viewService.ListChanged -= this.OnListChanged;
为新服务更新Startup
服务。
services.AddSingleton<WeatherForecastService>();
services.AddScoped<WeatherForecastViewService>();
更新FetchData
。它现在使用WeatherForecastList
组件。该按钮提供了一种更改列表和查看 UI 更新的机制。
@page "/fetchdata"
@using ***.Answers.Data
<WeatherForecastList/>
<div class="m-2">
<button class="btn btn-dark" @onclick="this.LoadRecords">Reload Records</button>
</div>
@code
[Inject] WeatherForecastViewService viewService get; set;
private bool isLong = true;
private async Task LoadRecords()
await this.viewService.GetForecastsAsync(isLong);
this.isLong = !this.isLong;
希望我第一次就搞定了所有代码!我相信有人会指出任何明显的错误或改进。
【讨论】:
以上是关于当 API 调用完成时,如何从 .RAZOR 主页面中的所有子组件触发/刷新我的主 .RAZOR 页面?的主要内容,如果未能解决你的问题,请参考以下文章