当 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 页面?的主要内容,如果未能解决你的问题,请参考以下文章

如何在网站上自动执行 api 请求

Chromecast - 完成投射后返回主页

从按钮单击调用MVC Razor Ajax

Razor基础,视图里如何调用controller里的函数

Asp.net Core 3.1-如何从控制器返回到 Razor 页面?

如何从服务器端 Blazor 应用程序中的 Blazor 组件调用 razor 页面而不导致页面刷新