ASP.NET MVC Kendo DropDownTree LoadOnDemand与过滤兼容方案咨询
解决ASP.NET MVC Kendo DropDownTree启用LoadOnDemand时的过滤与节点显示问题
核心思路
由于Kendo DropDownTree原生不支持服务端过滤,启用LoadOnDemand后过滤失效的根源是客户端仅能过滤已加载的节点。最优方案是:
- 前端触发过滤时,将过滤参数传给后端,后端返回所有匹配节点及其完整父级路径的树形数据
- 前端用返回的新数据替换数据源,同时自动展开匹配节点
- 页面初始化时预加载已保存节点的完整路径,确保已选节点能正常显示
步骤1:修正DropDownTree配置
启用LoadOnDemand,绑定Filtering事件,同时明确过滤模式:
@(Html.Kendo().DropDownTree() .Name("DropDownTree") .Filter(FilterType.Contains) .Checkboxes(true) .CheckAll(false) .DataTextField("Name") .DataValueField("id") .LoadOnDemand(true) // 启用按需加载 .Events(e => e.Filtering("onFiltering")) // 绑定过滤事件 .DataSource(source => { source .Custom() .Type("aspnetmvc-ajax") .Transport(transport => { transport.Read(x => x.Action("Read_DropDownTreeData", "DropDownTree") .Data("getAdditionalData")); // 传递初始加载的已选节点参数 }) .Schema(schema => { schema.Model(p => { p.Children("Items").HasChildren("hasChildren").Id("id"); }); }); }) )
步骤2:改造后端接口
后端需处理三种场景:初始加载根节点、过滤请求返回匹配节点及父级、预加载已选节点的完整路径:
public ActionResult Read_DropDownTreeData(string filterValue, List<int> selectedIds) { var orgData = GetFullOrganizationData(); // 从数据库获取完整组织架构 IEnumerable<OrgNode> result; if (!string.IsNullOrEmpty(filterValue)) { // 过滤匹配节点并构建完整父路径 var matchedNodes = orgData.Flatten(n => n.Items) .Where(n => n.Name.Contains(filterValue, StringComparison.OrdinalIgnoreCase)) .ToList(); result = BuildHierarchyWithParents(matchedNodes, orgData); } else if (selectedIds != null && selectedIds.Any()) { // 预加载已选节点的父路径 var selectedNodes = orgData.Flatten(n => n.Items) .Where(n => selectedIds.Contains(n.id)) .ToList(); result = BuildHierarchyWithParents(selectedNodes, orgData); } else { // 初始加载返回根节点 result = orgData.Where(n => n.ParentId == null); } return Json(result, JsonRequestBehavior.AllowGet); } // 辅助方法:构建包含父级的树形结构 private IEnumerable<OrgNode> BuildHierarchyWithParents(List<OrgNode> targetNodes, IEnumerable<OrgNode> fullData) { var allRequiredNodes = new HashSet<OrgNode>(targetNodes); // 递归添加所有父节点 foreach (var node in targetNodes) { AddParentNodes(node, fullData, allRequiredNodes); } // 重构树形结构 return allRequiredNodes.Where(n => n.ParentId == null) .Select(n => BuildNodeWithChildren(n, allRequiredNodes)); } private void AddParentNodes(OrgNode node, IEnumerable<OrgNode> fullData, HashSet<OrgNode> allNodes) { if (node.ParentId == null) return; var parent = fullData.Flatten(n => n.Items).FirstOrDefault(p => p.id == node.ParentId); if (parent != null && allNodes.Add(parent)) { AddParentNodes(parent, fullData, allNodes); } } private OrgNode BuildNodeWithChildren(OrgNode node, HashSet<OrgNode> allNodes) { var children = allNodes.Where(n => n.ParentId == node.id) .Select(c => BuildNodeWithChildren(c, allNodes)) .ToList(); return new OrgNode { id = node.id, Name = node.Name, ParentId = node.ParentId, hasChildren = children.Any() || node.hasChildren, Items = children }; } // 组织节点模型 public class OrgNode { public int id { get; set; } public string Name { get; set; } public int? ParentId { get; set; } public bool hasChildren { get; set; } public List<OrgNode> Items { get; set; } = new List<OrgNode>(); } // 扩展方法:扁平化树形结构 public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector) { foreach (var item in source) { yield return item; foreach (var child in childrenSelector(item).Flatten(childrenSelector)) { yield return child; } } }
步骤3:优化前端Filtering事件
替换数据源时需创建Kendo HierarchicalDataSource实例,而非直接传原始数据,同时展开所有匹配节点:
function onFiltering(e) { var filter = e.filter; var dropdowntree = $("#DropDownTree").data("kendoDropDownTree"); if (filter && filter.value) { e.preventDefault(); $.ajax({ url: "@Url.Action("Read_DropDownTreeData", "DropDownTree")", data: { filterValue: filter.value }, success: function (data) { // 创建新的层级数据源 var newDataSource = new kendo.data.HierarchicalDataSource({ data: data, schema: { model: { children: "Items", hasChildren: "hasChildren", id: "id" } } }); dropdowntree.setDataSource(newDataSource); dropdowntree.expand(".k-item"); // 展开所有节点方便查看 } }); } else { // 清空过滤值时重置为初始数据源 dropdowntree.dataSource.read(); } } // 传递已选节点参数(页面初始化用) function getAdditionalData() { // 假设已选节点ID存储在hidden字段中 var selectedIds = $("#SelectedOrgIds").val().split(",").map(Number); return { selectedIds: selectedIds }; }
步骤4:处理已保存节点显示
页面初始化时,getAdditionalData会传递已选节点ID给后端,后端返回这些节点的完整父路径,确保DropDownTree能正确显示已选节点的层级。
关键注意事项
- 后端构建树形结构时,需保留
hasChildren字段的正确状态,确保LoadOnDemand仍能正常工作 - 前端过滤后展开所有节点,避免用户需要手动查找匹配结果
- 初始加载预加载已选节点的父路径,解决已保存节点因未加载而无法显示的问题
内容的提问来源于stack exchange,提问作者MKEF
相关产品推荐
相关产品推荐

