Blazor WebAssembly多API调用致页面加载缓慢 求优化方案
优化Blazor WebAssembly页面加载性能方案
我正在开发Blazor WebAssembly应用,解决方案包含前端Blazor WASM项目和后端.NET Core WebAPI,目前遇到页面加载过慢的问题:在某个Razor页面的OnInitializedAsync方法中,串行调用了多个返回6万+条数据的API接口,导致页面加载耗时过长,但页面加载完成后的增删改操作速度正常。使用EntityFramework Core作为数据访问框架,现寻求合理的优化方案。
原核心代码如下:
protected override async Task OnInitializedAsync() { try { // Lists MasterLocalMemberList = await Http.GetFromJsonAsync<List<MemberLocal>>("api/member/getlocalmembers"); MasterInternationalMembeList = await Http.GetFromJsonAsync<List<MemberInternational>>("api/member/getinternationalmembers"); MasterVisitorList = await Http.GetFromJsonAsync<List<Visitor>>("api/member/getvisitors"); MasterTablighGuestList = await Http.GetFromJsonAsync<List<TablighGuest>>("api/member/gettablighguests"); AttendanceList = await Http.GetFromJsonAsync<List<Attendance>>("api/member/getattendance"); CountryList = await Http.GetFromJsonAsync<List<MemberCountry>>("api/member/getcountries"); GateList = await Http.GetFromJsonAsync<List<Gate>>("api/member/getgatelist"); gateNumber = await SessionStorage.GetItemAsync<int>("GateNumber"); gateNumber = (gateNumber == null ? 1 : gateNumber); GateType = GateList.Where(x => x.GateID == gateNumber).First().GateType; //await mudSelectItemCountry.SelectOption(SelectedCountry); UpdateMembersListByCountry(); } catch (Exception emx) { //throw; } }
一、并行调用API接口
当前串行等待每个API返回的方式会累加请求耗时,改成并行调用可大幅压缩总加载时间,用Task.WhenAll同时发起所有请求,等待全部完成后再处理结果:
protected override async Task OnInitializedAsync() { try { // 并行发起所有API请求,通过ContinueWith赋值到对应列表 var tasks = new List<Task> { Http.GetFromJsonAsync<List<MemberLocal>>("api/member/getlocalmembers").ContinueWith(t => MasterLocalMemberList = t.Result), Http.GetFromJsonAsync<List<MemberInternational>>("api/member/getinternationalmembers").ContinueWith(t => MasterInternationalMembeList = t.Result), Http.GetFromJsonAsync<List<Visitor>>("api/member/getvisitors").ContinueWith(t => MasterVisitorList = t.Result), Http.GetFromJsonAsync<List<TablighGuest>>("api/member/gettablighguests").ContinueWith(t => MasterTablighGuestList = t.Result), Http.GetFromJsonAsync<List<Attendance>>("api/member/getattendance").ContinueWith(t => AttendanceList = t.Result), Http.GetFromJsonAsync<List<MemberCountry>>("api/member/getcountries").ContinueWith(t => CountryList = t.Result), Http.GetFromJsonAsync<List<Gate>>("api/member/getgatelist").ContinueWith(t => GateList = t.Result) }; // 等待所有请求完成 await Task.WhenAll(tasks); // 后续逻辑保持不变(修正int类型null判断问题) gateNumber = await SessionStorage.GetItemAsync<int>("GateNumber"); gateNumber = gateNumber == 0 ? 1 : gateNumber; GateType = GateList.First(x => x.GateID == gateNumber).GateType; UpdateMembersListByCountry(); } catch (Exception emx) { // 补充异常处理逻辑 } }
二、分页/按需加载数据
6万+条数据一次性返回会占用大量带宽和前端内存,改成分页加载:
- 后端API添加
pageIndex、pageSize参数,用EF Core的Skip()+Take()实现分页查询 - 前端使用分页组件(如MudBlazor的MudPagination)加载当前页数据,或监听滚动事件实现滚动加载
- 仅加载页面当前需要的数据,而非全量数据
后端分页接口示例:
[HttpGet("getlocalmembers")] public async Task<IActionResult> GetLocalMembers(int pageIndex = 1, int pageSize = 100) { var totalCount = await _context.MemberLocals.CountAsync(); var data = await _context.MemberLocals .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync(); return Ok(new { TotalCount = totalCount, Data = data }); }
三、后端合并API端点
减少HTTP请求次数,创建一个统一的初始化接口,一次性返回页面所需所有数据:
- 定义包含所有列表的DTO类
- 后端并行查询所有表数据,打包后返回
- 前端仅需调用一次接口即可获取全部初始化数据
示例DTO与后端接口:
// 定义初始化数据DTO public class PageInitDataDto { public List<MemberLocal> LocalMembers { get; set; } public List<MemberInternational> InternationalMembers { get; set; } public List<Visitor> Visitors { get; set; } public List<TablighGuest> TablighGuests { get; set; } public List<Attendance> Attendances { get; set; } public List<MemberCountry> Countries { get; set; } public List<Gate> Gates { get; set; } } // 统一初始化接口 [HttpGet("getpageinitdata")] public async Task<IActionResult> GetPageInitData() { // 并行查询所有表数据 var localTask = _context.MemberLocals.ToListAsync(); var internationalTask = _context.MemberInternationals.ToListAsync(); var visitorTask = _context.Visitors.ToListAsync(); var tablighTask = _context.TablighGuests.ToListAsync(); var attendanceTask = _context.Attendances.ToListAsync(); var countryTask = _context.MemberCountries.ToListAsync(); var gateTask = _context.Gates.ToListAsync(); await Task.WhenAll(localTask, internationalTask, visitorTask, tablighTask, attendanceTask, countryTask, gateTask); var initData = new PageInitDataDto { LocalMembers = await localTask, InternationalMembers = await internationalTask, Visitors = await visitorTask, TablighGuests = await tablighTask, Attendances = await attendanceTask, Countries = await countryTask, Gates = await gateTask }; return Ok(initData); }
前端调用示例:
var initData = await Http.GetFromJsonAsync<PageInitDataDto>("api/member/getpageinitdata"); MasterLocalMemberList = initData.LocalMembers; MasterInternationalMembeList = initData.InternationalMembers; MasterVisitorList = initData.Visitors; // 其他列表赋值...
四、EF Core查询优化
- 关闭跟踪:对只读数据使用
AsNoTracking(),减少EF Core的跟踪开销 - 投影查询:用
Select()只查询页面需要的字段,避免加载实体中无关属性 - 添加索引:给查询中常用的过滤、排序字段添加数据库索引,提升查询速度
优化后的EF Core查询示例:
var data = await _context.MemberLocals .AsNoTracking() .Select(m => new MemberLocalDto { Id = m.Id, Name = m.Name, Phone = m.Phone // 仅保留页面需要的字段 }) .ToListAsync();
五、前端数据缓存
如果数据不会频繁更新,可将数据缓存到前端,避免每次页面加载重复请求:
- 使用
SessionStorage或LocalStorage缓存数据,设置合理的过期时间 - 页面初始化时优先读取缓存,缓存不存在时再调用API
缓存逻辑示例:
protected override async Task OnInitializedAsync() { // 读取本地缓存 var cachedLocalMembers = await SessionStorage.GetItemAsync<List<MemberLocal>>("LocalMembersCache"); if (cachedLocalMembers != null) { MasterLocalMemberList = cachedLocalMembers; } else { MasterLocalMemberList = await Http.GetFromJsonAsync<List<MemberLocal>>("api/member/getlocalmembers"); // 写入缓存,可添加过期时间逻辑 await SessionStorage.SetItemAsync("LocalMembersCache", MasterLocalMemberList); } // 其他列表缓存逻辑同理 }
六、添加加载状态提升用户体验
在页面加载过程中显示加载动画,避免用户误以为页面卡顿:
<!-- 页面中添加加载组件 --> @if (IsLoading) { <div class="loading-overlay"> <span>加载中...</span> </div> } else { <!-- 页面主体内容 --> }
protected bool IsLoading { get; set; } protected override async Task OnInitializedAsync() { IsLoading = true; try { // 数据加载逻辑 } finally { IsLoading = false; } }
内容的提问来源于stack exchange,提问作者Shakil Ahmed
相关产品推荐
相关产品推荐

