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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 23:20:15