Blazor客户端托管项目筛选后组件数据不刷新问题解决
最近在做Blazor客户端托管项目时碰到个糟心的问题:原本能正常从API加载数据的表格,添加筛选功能后,点击搜索按钮,控制台显示PersonList的数量已经变了,但页面上的表格就是纹丝不动。折腾了好一会儿才找到问题根源,给大家分享下排查和解决过程。
问题场景
我项目里的代码结构大概是这样的:
前端组件(index.razor)
继承自PersonBase,包含筛选输入框和表格,点击按钮调用LoadItems方法加载数据:
@inherits PersonBase; <h2>list size: @PersonList.Count()</h2> <EditForm Model="@Obj "> <div class="col-md-12"> <div class="well"> <table class='table borderless'> <tr> <td colspan="2" style="text-align: right"> <InputText @bind-Value="@Search" class="form-control input-sm" style="width:200px;margin-bottom:0px" type="text" name="input3" placeholder="Pesquisar por:" /> <button class="btn btn-info" @onclick="@(() => LoadItems(@Search))"> <i class="fas fa-search"></i> </button> </td> </tr> </table> </div> </div> </EditForm> <table class="table"> <thead> <tr> <th>Id</th> <th>Name</th> </tr> </thead> <tbody> @foreach (var person in PersonList) { <tr> <td>@person.Id</td> <td>@person.Name</td> </tr> } </tbody> </table>
组件基类(PersonBase.cs)
负责调用API加载数据,并且已经手动调用了StateHasChanged()通知组件更新:
namespace MyProject.Client.Pages.PersonComponent { public class PersonBase : ComponentBase { [Inject] public HttpClient Http { get; set; } [Inject] public NavigationManager Navigation { get; set; } [Parameter] public string Search { get; set; } = string.Empty; protected IEnumerable<Person> Itens { get; set; } [Parameter] public List<Person> PersonList { get; set; } public int idObj; [Parameter] public Person Obj { get; set; } protected override async Task OnInitializedAsync() { Obj = new Person(); await LoadItems(null); Console.WriteLine("OnInitializedAsync => " + PersonList.Count); } protected async Task LoadItems(string search) { try { var httpResponse = await Http.GetAsync($"api/person/list/{search}"); if (httpResponse.IsSuccessStatusCode) { var responseString = await httpResponse.Content.ReadAsStringAsync(); PersonList = JsonSerializer.Deserialize<List<Person>>(responseString, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); Itens = PersonList; Console.WriteLine("LoadItems => PersonList => " + PersonList.Count); await InvokeAsync(() => StateHasChanged()).ConfigureAwait(false); } } catch (Exception) { throw; } } } }
API控制器(PersonController.cs)
原本的路由定义是把search作为路由参数:
[HttpGet("list/{search}")] public async Task<ActionResult<List<Person>>> GetByAll(string search) { try { if (string.IsNullOrEmpty(search)) { return await _context.Person .AsNoTracking() .Take(3) .ToListAsync(); } else { return await _context.Person .AsNoTracking() .Where(n => n.Name.ToLower().Contains(search.ToLower())) .Take(3) .ToListAsync(); } } catch (Exception) { throw; } }
现象是:页面初始加载3条数据正常,但输入筛选关键词点击搜索后,控制台打印的PersonList.Count是正确的筛选后数量,但页面表格就是不更新。
排查与解决
一开始我以为是Blazor的组件更新问题,检查了StateHasChanged()也调用了,PersonList也重新赋值了,前端逻辑没问题。那问题肯定出在API这边——是不是API没正确接收到筛选参数,导致返回的还是初始数据?
仔细看API的路由定义:[HttpGet("list/{search}")],这里是把search作为路由参数,但当我调用Http.GetAsync($"api/person/list/{search}")时,如果search是空字符串,请求路径会变成api/person/list/,这会导致路由匹配异常;而且就算有值,这种方式也不如查询字符串直观,更关键的是,API没有显式指定参数的绑定来源。
修正方案
- 调整API控制器的路由和参数绑定
把路由改成查询字符串模式,用[FromQuery]特性明确指定参数来自查询字符串:
[HttpGet] [Route("list")] public async Task<ActionResult<List<Person>>> GetByAll([FromQuery]string search = "") { try { if (string.IsNullOrEmpty(search)) { return await _context.Person .AsNoTracking() .Take(3) .ToListAsync(); } else { return await _context.Person .AsNoTracking() .Where(n => n.Name.ToLower().Contains(search.ToLower())) .Take(3) .ToListAsync(); } } catch (Exception) { throw; } }
- 调整前端的API请求方式
把原来的路由参数请求改成查询字符串请求:
var httpResponse = await Http.GetAsync($"api/person/list?search={search}");
改完之后再测试,筛选功能正常了,表格也能实时更新新的数据列表了!
总结
这个问题的核心是API参数绑定方式错误,导致筛选参数没有被正确接收,API返回的还是初始数据,所以前端看起来表格没更新。Blazor这边的组件更新逻辑是没问题的,只要API返回了正确的筛选后数据,前端就能正常渲染。
内容的提问来源于stack exchange,提问作者Diego Venâncio

