我正在开发一款可让用户搜索我们的数据库的应用程序。当用户输入搜索词时,应用程序会访问 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()
但是,在从
SearchResults.razor
中的表单条目中首次加载 Index.razor
后,似乎没有任何效果。
我需要做什么才能让它发挥作用?它看起来很简单,但我就是无法弄清楚。
答案展示了如何更新 Blazor WeatherForecast 应用程序以演示状态/通知模式以及如何在组件中使用它。我使用了天气预报应用程序,因为您的问题没有足够的详细信息,无法使用您的代码作为答案的基础,而天气预报应用程序提供了一个很好的构建模板。
起点是标准 Blazor Server 模板项目。我的名字叫
StackOverflow.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 StackOverflow.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 StackOverflow.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
的列表。它没有自己的副本 - 所有组件都使用相同的列表。ListChanged
事件并在触发 evwnt 时调用 StateHasChanged
。@implements IDisposable
@using StackOverflow.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 StackOverflow.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;
}
}
希望我第一次就把所有代码都正确了!我确信有人会指出任何明显的错误或改进。