You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 04:35:59