到目前为止,.NET 5已经出来了,Blazor也迎来了重大更新,之后的学习就要基于最新的.NET 5了
这次主要基于Web Assembly实现一个全局的加载指示器,她主要应用于两种场景:
- 应用第一次加载
- HTTP请求
1. 应用加载
众所周知,Web Assembly体积比较大,因此第一次加载会比较耗时,默认的程序模板中,在index.html页面里面有这么一段:
其实她就是一个简化版的加载指示器,她的作用就是在程序加载完之前在页面上显示文字"Loading",虽然功能有了,但是还是寒酸了一点。
1.1 准备
网上有很多加载指示器的样式代码,挑选自己喜欢的即可,我找了Bootstrap 5里面的Spinner
https://v5.getbootstrap.com/docs/5.0/components/spinners/#growing-spinner
Bootstrap 5.0目前还是alpha阶段
1.2 修改index.html
先贴代码:
<head>
<link href="xxxxx/bootstrap.min.css" rel="stylesheet"/>
<style>
#globalLoadingSpinnerBg, #globalLoadingSpinner {
display:none;
}
.loading #globalLoadingSpinnerBg {
display:block;
}
.loading #globalLoadingSpinner {
display: flex;
}
</style>
</head>
<body class="loading">
<div id="globalLoadingSpinnerBg" class="modal-backdrop fade show"></div>
<div id="globalLoadingSpinner" class="modal fade show" role="dialog">
<div class="text-center;" style="margin: auto;">
<div class="spinner-grow text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-secondary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-success" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-danger" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-warning" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-light" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-dark" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
<div id="app"></div>
</body>
要点:
- 引入Bootstrap的样式表
- 添加指示器的HTML代码,注意是作为body里面的第一个元素,这样才能保证在加载Blazor程序集之前显示。这里把指示器放入了模态对话框里面,目的是想在加载的时候阻止用户的其他操作,比如频繁的重复点击按钮等。
- 为body添加样式名“loading", 通过head里面的样式定义可以看到,当为body添加“loading”样式的时候,显示指示器,否则隐藏指示器。
这样只要页面开始加载的时候就会显示加载指示器。
1.3 隐藏指示器
如何隐藏指示器?
其实只要移除body里面的loading样式名就可以了,我们可以借助Js的互操作,通过javascript函数来实现。
function hideGlobalLoadingSpinner() {
document.body.classList.remove('loading');
}
何时隐藏?
回顾一下Blazor的页面周期, 我们需要在页面组件全部加载完以后隐藏指示器,那么可以选择的有OnAfterRender,OnAfterRenderAsync。
由于App组件是整个Blazor的根组件,因此我们可以修改她的OnAfterRenderAsync,调用上面的JS函数:hideGlobalLoadingSpinner。
protected async override Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
await jsRuntime.InvokeVoidAsync("hideGlobalLoadingSpinner");
}
2. HTTP请求
Web Assembly应用离不开HTTP请求,那么如何在请求开始前显示加载指示,请求结束后隐藏呢?
显然不可能每个HTTP请求都写代码进行这些操作,幸运的是.NET Core提供了IHttpClientFactory,可以帮助我们在请求的上下文中嵌入自己的处理程序。
2.1 封装Javascript调用
前面已经有了隐藏指示器的Javascript函数,我们还需要一个函数来显示指示器, 很简单,为body添加loading样式名就行了:
function showGlobalLoadingSpinner() {
document.body.classList.add('loading');
}
为了隐藏Javascript的调用细节,并方便在组件内使用,有必要封装成c#类。
先定义一个接口,包含显示和隐藏两个方法:
public interface IGlobalLoadingSpinner
{
Task ShowAsync();
Task HideAsync();
}
接下来看实现:
public class DefaultGlobalLoadingSpinner : IGlobalLoadingSpinner
{
static object Locker = new object();
int SpinnerCount = 1;
IJSRuntime _jsRuntime;
public DefaultGlobalLoadingSpinner(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task ShowAsync()
{
lock (Locker)
{
SpinnerCount++;
}
await this._jsRuntime.InvokeVoidAsync("showGlobalLoadingSpinner");
}
public async Task HideAsync()
{
lock (Locker)
{
SpinnerCount--;
if (SpinnerCount < 0)
{
SpinnerCount = 0;
}
}
if (SpinnerCount == 0)
{
await this._jsRuntime.InvokeVoidAsync("hideGlobalLoadingSpinner");
}
}
}
要点:
- 注入IJSRuntime,方便Javascript函数的调用。
- 加入Locker以及SpinnerCount,这里的考虑是可能一次发起多个HTTP请求,可能会调用多次Show函数,因此我们使用计数器来记录请求数量的变化,当计数器变为0的时候才真正隐藏指示器。
- SpinnerCount的初始值为什么是1? 因为index.html刚加载的时候是默认显示指示器的。
如何使用该接口?
首先修改Program.cs,注入IGlobalLoadingSpinner
builder.Services.AddSingleton<IGlobalLoadingSpinner, DefaultGlobalLoadingSpinner>();
然后在组件内注入该接口,比如上面的App组件我们可以修改为
@inject IGlobalLoadingSpinner globalLoadingSpinner
protected async override Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
await globalLoadingSpinner.HideAsync();
}
2.2 添加自定义HTTP处理程序
该处理程序用来在HTTP请求的上下文中显示和隐藏加载指示器:
public class LoadingSpinnerMessageHandler : DelegatingHandler
{
private readonly ILogger<LoadingSpinnerMessageHandler> _logger;
private readonly IGlobalLoadingSpinner _loadingSpinnerService;
public LoadingSpinnerMessageHandler(ILogger<LoadingSpinnerMessageHandler> logger, IGlobalLoadingSpinner loadingSpinnerService)
{
_logger = logger;
_loadingSpinnerService = loadingSpinnerService;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//发送请求之前显示加载指示器
await _loadingSpinnerService.ShowAsync();
//发送请求
var response = await base.SendAsync(request, cancellationToken);
//收到结果之后隐藏加载指示器
await _loadingSpinnerService.HideAsync();
return response;
}
}
自定义处理程序继承自DelegatingHandler(来自命名空间System.Net.Http),主要实现了SendAsync方法,该方面里面使用前面定义的接口来显示和隐藏加载指示器。
2.3 使用HttpClient请求
Blazor里面的Http请求都需要用到HttpClient,这里我们利用HttpClientFactory来创建HttpClient了,首先还是修改Program.cs:
//注入前面创建的处理程序
builder.Services.AddTransient<LoadingSpinnerMessageHandler>();
//定义HttpClientFactory,并添加处理程序
//这里使用了命名的HttpClient,当然你也可以定义所有HttpClient全部使用该处理程序
//详情查看:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0
//需要先安装包Microsoft.Extensions.Http
builder.Services.AddHttpClient("sampleapi", client =>
{
client.BaseAddress = new Uri("http://localhost:5000");
})
.AddHttpMessageHandler<LoadingSpinnerMessageHandler>();
组件内使用也很简单:
@inject IHttpClientFactory httpFac
protected async Task LoadData()
{
//通过名称创建HttpClient
var httpClient = this.httpFac.CreateClient("sampleapi");
//发送请求的时候,自动显示和隐藏加载指示器
var forecasts = await httpClient.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}