使用PagedList分页+多复选框筛选时如何缩短URL?
你的问题很典型——当用多个复选框做筛选时,Html.CheckBoxFor的默认行为(生成隐藏输入)和零散的布尔参数会让查询字符串变得又长又乱,后续加更多筛选字段只会雪上加霜。下面是几个实用的优化方案,从根源解决URL问题:
1. 先搞定重复参数的根源:替换Html.CheckBoxFor
Html.CheckBoxFor会生成两个同名输入(一个复选框+一个隐藏的value=false输入),这就是URL里出现IsResYearChecked=true&IsResYearChecked=false的原因。我们可以手动编写复选框,避免生成冗余的隐藏输入:
<!-- 替换原来的@Html.CheckBoxFor --> <input type="checkbox" name="IsResYearChecked" id="IsResYearChecked" @(Model.IsResYearChecked ? "checked" : "") /> <label for="IsResYearChecked">Filter by Resolution Year</label>
这样只有当复选框被勾选时,参数才会出现在查询字符串里,未勾选的字段直接消失,能减少一半左右的冗余参数。
2. 合并筛选字段参数:用集合代替多个布尔属性
如果要添加更多筛选字段,用单个集合参数代替一堆布尔属性是更优雅的方案,能大幅减少URL中的参数数量。
修改视图模型
把分散的布尔属性换成一个集合,用来记录选中的筛选字段:
public class SearchPagingViewModels { public IPagedList<AllResolution> Resolutions { get; set; } public string Keyword { get; set; } // 用集合存储选中的筛选字段,避免多个布尔参数 public List<string> SelectedFilterFields { get; set; } = new List<string>(); // 定义常量避免硬编码字段名 public const string Field_Year = "ResolutionYear"; public const string Field_Number = "ResolutionNumber"; public const string Field_Text = "ResolutionText"; }
调整控制器逻辑
用动态构建的LINQ条件来处理集合参数(这里用到LinqKit的PredicateBuilder,可以NuGet安装LinqKit包):
public ViewResult Index(int? page, string keyword, List<string> selectedFilterFields) { int pageSize = 25; int pageNumber = page ?? 1; IQueryable<AllResolution> resolutions = db.AllResolutions; if (!string.IsNullOrEmpty(keyword) && selectedFilterFields?.Any() == true) { // 动态构建OR筛选条件 var filterPredicate = PredicateBuilder.False<AllResolution>(); if (selectedFilterFields.Contains(SearchPagingViewModels.Field_Year)) { filterPredicate = filterPredicate.Or(x => x.ResolutionYear.Contains(keyword)); } if (selectedFilterFields.Contains(SearchPagingViewModels.Field_Number)) { filterPredicate = filterPredicate.Or(x => x.ResolutionNumber.Contains(keyword)); } if (selectedFilterFields.Contains(SearchPagingViewModels.Field_Text)) { filterPredicate = filterPredicate.Or(x => x.ResolutionText.Contains(keyword)); } resolutions = resolutions.Where(filterPredicate); } resolutions = resolutions.OrderBy(c => c.ResolutionYear).ThenBy(c => c.ResolutionNumber); var model = new SearchPagingViewModels { Keyword = keyword, SelectedFilterFields = selectedFilterFields ?? new List<string>(), Resolutions = resolutions.ToPagedList(pageNumber, pageSize) }; return View(model); }
更新视图的复选框
让所有复选框共享同一个name,值为对应的字段名:
@using (Html.BeginForm("Index", "Resolutions", FormMethod.Get)) { @Html.LabelFor(m => m.Keyword) @Html.TextBoxFor(m => m.Keyword) <div> <input type="checkbox" name="SelectedFilterFields" id="Filter_Year" value="@SearchPagingViewModels.Field_Year" @(Model.SelectedFilterFields.Contains(SearchPagingViewModels.Field_Year) ? "checked" : "") /> <label for="Filter_Year">Filter by Resolution Year</label> </div> <div> <input type="checkbox" name="SelectedFilterFields" id="Filter_Number" value="@SearchPagingViewModels.Field_Number" @(Model.SelectedFilterFields.Contains(SearchPagingViewModels.Field_Number) ? "checked" : "") /> <label for="Filter_Number">Filter by Resolution Number</label> </div> <!-- 新增筛选字段只需复制上面的结构即可 --> <input type="submit" value="Search" /> }
此时URL会变成简洁的形式:.../Index?Keyword=someText&SelectedFilterFields=ResolutionYear&SelectedFilterFields=ResolutionText
3. 进一步缩短URL:把分页参数移到路由里
通过修改路由配置,将page参数从查询字符串放到URL路径中,能进一步减少查询字符串的长度:
在RouteConfig.cs中添加自定义路由(放在默认路由之前):
routes.MapRoute( name: "ResolutionsPaging", url: "Resolutions/Index/{page}", defaults: new { controller = "Resolutions", action = "Index", page = UrlParameter.Optional } );
现在分页URL会变成:/Resolutions/Index/2?Keyword=someText&SelectedFilterFields=ResolutionYear
4. 终极优化:用逗号分隔字符串代替集合参数
如果还想让URL更紧凑,可以把集合参数改成逗号分隔的字符串:
修改视图模型
public string SelectedFilterFields { get; set; }
控制器处理
// 将逗号分隔字符串转为列表 var selectedFieldsList = !string.IsNullOrEmpty(selectedFilterFields) ? selectedFilterFields.Split(',').ToList() : new List<string>(); // 后续筛选逻辑和之前一致,用selectedFieldsList判断
视图用JS处理提交
添加隐藏字段存储拼接后的字符串,在表单提交前处理:
<input type="hidden" name="SelectedFilterFields" id="SelectedFieldsHidden" /> <script> $('form').submit(function() { var selected = []; $('input[name="FilterCheckbox"]').each(function() { if ($(this).is(':checked')) { selected.push($(this).val()); } }); $('#SelectedFieldsHidden').val(selected.join(',')); // 移除原复选框的name,避免重复提交 $('input[name="FilterCheckbox"]').attr('name', ''); return true; }); </script>
同时把复选框的name改成FilterCheckbox:
<input type="checkbox" name="FilterCheckbox" id="Filter_Year" value="@SearchPagingViewModels.Field_Year" @(Model.SelectedFilterFields?.Split(',').Contains(SearchPagingViewModels.Field_Year) == true ? "checked" : "") />
最终URL会非常简洁:/Resolutions/Index/2?Keyword=someText&SelectedFilterFields=ResolutionYear,ResolutionText
内容的提问来源于stack exchange,提问作者kevorski

