Back to Devexpress

Bind Blazor TreeList to Data

blazor-404976-components-treelist-bind-to-data.md

latest52.7 KB
Original Source

Bind Blazor TreeList to Data

  • Jan 20, 2026
  • 24 minutes to read

This document describes how to bind the Blazor TreeList component to data in different scenarios.

Note

The TreeList component does not support binding to fields with the same name for different types.

Flat Data

You can bind the TreeList component to a flat data source that implements the IEnumerable<T> or IListSource interface. To build a hierarchical structure of TreeList nodes, specify additional properties that define node relationships:

KeyFieldNameA field name that contains node unique identifiers.ParentKeyFieldName

A field name that contains a node’s parent identifier.

For root nodes, this field’s value matches RootValue. If the RootValue property is not specified (set to null), root nodes are nodes whose parent identifier does not point to any node.

Note

Data types of RootValue and data source fields assigned to KeyFieldName and ParentKeyFieldName properties should match.

Follow the steps below to bind the TreeList component to a data source that contains flat data:

  1. Bind the Data parameter to a C# property.
  2. Populate this property with data in the OnInitialized or OnInitializedAsync lifecycle method.
  3. Assign names of data source fields to KeyFieldName and ParentKeyFieldName properties.
  4. Optional. Specify the RootValue property if the parent identifier of root nodes differs from 0 and null.

The following example binds DxTreeList to a flat data source:

razor
@inject EmployeeTaskService EmployeeTaskService

<DxTreeList Data="TreeListData" KeyFieldName="Id" ParentKeyFieldName="ParentId">
    <Columns>
        <DxTreeListDataColumn FieldName="Name" Caption="Task" />
        <DxTreeListDataColumn FieldName="EmployeeName" />
        <DxTreeListDataColumn FieldName="StartDate" />
        <DxTreeListDataColumn FieldName="DueDate" />
    </Columns>
</DxTreeList>

@code {
    List<EmployeeTask> TreeListData { get; set; }

    protected override void OnInitialized() {
        TreeListData = EmployeeTaskService.GenerateData();
    }
}
csharp
public class EmployeeTask {
    public int Id { get; set; }
    public int ParentId { get; set; }
    public string Name { get; set; }
    public string EmployeeName { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime DueDate { get; set; }
    public int Progress { get; set; }
    public EmployeeTask() { }
    public EmployeeTask(
        int id,
        int parentId,
        string name,
        string employeeName,
        DateTime startDate,
        DateTime dueDate,
        int progress
        )
    {
        Id = id;
        ParentId = parentId;
        Name = name;
        EmployeeName = employeeName;
        StartDate = startDate;
        DueDate = dueDate;
        Progress = progress;
    }
}
csharp
public class EmployeeTaskService {
    public List<EmployeeTask> GenerateData() {
        return new List<EmployeeTask>() {
            new EmployeeTask(1, 0, "Simplify & Clarify Product Messaging", "John Heart", new DateTime(2018, 4, 3), new DateTime(2018, 4, 14), 14),
            new EmployeeTask(2, 1, "Prepare Financial Reports", "Samantha Bright", new DateTime(2018, 4, 3), new DateTime(2018, 4, 7), 17),
            new EmployeeTask(3, 1, "Prepare Marketing Plan", "Arthur Miller", new DateTime(2018, 4, 7), new DateTime(2018, 4, 14), 11),
            new EmployeeTask(4, 0, "Create Action Plan to Improve Customer Engagement", "Robert Reagan", new DateTime(2017, 8, 8), new DateTime(2018, 4, 8), 23),
            new EmployeeTask(5, 4, "Update Personnel Files", "Greta Sims", new DateTime(2017, 8, 8), new DateTime(2017, 10, 18), 100),
            new EmployeeTask(6, 4, "Review Health Insurance Options", "Brett Wade", new DateTime(2017, 9, 27), new DateTime(2017, 11, 10), 37),
            new EmployeeTask(7, 4, "Choose Between PPO and HMO Health Plan", "Sandra Johnson", new DateTime(2017, 12, 13), new DateTime(2018, 3, 23), 17),
            new EmployeeTask(8, 4, "Update Google Adwords Strategy", "Ed Holmes", new DateTime(2017, 8, 23), new DateTime(2017, 12, 23), 45),
            new EmployeeTask(9, 4, "Create New Brochure Design", "Barb Banks", new DateTime(2018, 1, 3), new DateTime(2018, 3, 14), 17),
            new EmployeeTask(10, 4, "Obtain Price Quote for New Brochure", "Kevin Carter", new DateTime(2018, 2, 1), new DateTime(2018, 3, 15), 18),
            new EmployeeTask(11, 4, "Brochure Design Review", "Cindy Stanwick", new DateTime(2017, 8, 22), new DateTime(2017, 10, 28), 4),
            new EmployeeTask(12, 4, "Review Website Redesign Strategy", "Sammy Hill", new DateTime(2017, 9, 16), new DateTime(2018, 3, 6), 73),
            new EmployeeTask(13, 4, "Roll Out New Website", "Davey Jones", new DateTime(2017, 11, 7), new DateTime(2018, 2, 6), 9),
            new EmployeeTask(14, 4, "Update Sales/Marketing Strategy", "Victor Norris", new DateTime(2017, 12, 13), new DateTime(2018, 4, 2), 5),
            new EmployeeTask(15, 4, "Update Sales/Revenue Report", "Mary Stern", new DateTime(2017, 12, 25), new DateTime(2018, 4, 2), 4),
            new EmployeeTask(16, 4, "Direct vs Online Sales Comparison Report", "Robin Cosworth", new DateTime(2018, 1, 2), new DateTime(2018, 3, 20), 14),
            new EmployeeTask(17, 4, "Review Sales Report and Approve Modifications", "Kelly Rodriguez", new DateTime(2017, 9, 4), new DateTime(2017, 10, 30), 8),
            new EmployeeTask(18, 4, "Update R&D Strategy", "James Anderson", new DateTime(2017, 11, 13), new DateTime(2017, 12, 4), 12),
            new EmployeeTask(19, 4, "Discuss Updated R&D Strategy", "Antony Remmen", new DateTime(2017, 10, 29), new DateTime(2017, 12, 31), 14),
            new EmployeeTask(20, 4, "Update QA Strategy", "Olivia Peyton", new DateTime(2017, 10, 31), new DateTime(2017, 11, 2), 18),
            new EmployeeTask(21, 4, "Schedule Training Events", "Taylor Riley", new DateTime(2017, 11, 19), new DateTime(2018, 4, 7), 21),
            new EmployeeTask(22, 4, "Approve Hiring of John Jeffers", "Amelia Harper", new DateTime(2018, 1, 7), new DateTime(2018, 4, 8), 10),
            new EmployeeTask(23, 0, "Increase Average Subscription Price", "Wally Hobbs", new DateTime(2017, 8, 9), new DateTime(2017, 9, 13), 96),
            new EmployeeTask(24, 23, "Update Non-Compete Agreements", "Brad Jameson", new DateTime(2017, 8, 9), new DateTime(2017, 9, 3), 96),
            new EmployeeTask(25, 23, "Update Employee Records with New NDA", "Karen Goodson", new DateTime(2017, 8, 23), new DateTime(2018, 9, 10), 100)
        };
    }
}
csharp
// ...
builder.Services.AddSingleton<EmployeeTaskService>();

Run Demo: TreeList - Flat Data

Hierarchical Data

You can bind the TreeList component to hierarchical data structures that implement the IEnumerable<T> interface.

Specify Node Children Declaratively

If each node in your data source includes a field with a list of child nodes, follow the steps below:

  1. Bind the Data parameter to a C# property.
  2. Populate this property with data in the OnInitialized or OnInitializedAsync lifecycle method.
  3. Assign the field name that stores children to the ChildrenFieldName property.

The following example binds the TreeList component to the SpaceObject collection. An object’s Satellites property stores child items.

razor
@inject SpaceObjectDataProvider SpaceObjectDataProvider

<DxTreeList Data="TreeListData" ChildrenFieldName="Satellites">
    <Columns>
        <DxTreeListDataColumn FieldName="Name" />
        <DxTreeListDataColumn FieldName="TypeOfObject" Caption="Type" />
        <DxTreeListDataColumn FieldName="Mass10pow21kg" Caption="Mass, kg" DisplayFormat="N2">
            <HeaderCaptionTemplate>Mass, 10<sup>21</sup> &#215; kg</HeaderCaptionTemplate>
        </DxTreeListDataColumn>
        <DxTreeListDataColumn FieldName="MeanRadiusInKM" Caption="Radius, km" DisplayFormat="N2"/>
        <DxTreeListDataColumn FieldName="Volume10pow9KM3" DisplayFormat="N2">
            <HeaderCaptionTemplate>Volume, 10<sup>9</sup> &#215; km<sup>3</sup></HeaderCaptionTemplate>
        </DxTreeListDataColumn>
        <DxTreeListDataColumn FieldName="SurfaceGravity" DisplayFormat="N2">
            <HeaderCaptionTemplate>Gravity, m/s<sup>2</sup></HeaderCaptionTemplate>
        </DxTreeListDataColumn>
    </Columns>
</DxTreeList>

@code {
    object TreeListData { get; set; }

    protected override async Task OnInitializedAsync() {
        TreeListData = SpaceObjectDataProvider.GenerateData();
    }
}
csharp
using System.Collections.Generic;

public class SpaceObject {
    public string Name { get; set; }
    public double MeanRadiusInKM { get; set; }
    public double Volume10pow9KM3 { get; set; }
    public double Mass10pow21kg { get; set; }
    public double Density { get; set; }
    public double SurfaceGravity { get; set; }
    public string TypeOfObject { get; set; }
    public List<SpaceObject> Satellites { get; set; }
    public SpaceObject(
        string name,
        double meanRadiusInKM,
        double volume10pow9KM3,
        double mass10pow21kg,
        double density,
        double surfaceGravity,
        string typeOfObject,
        List<SpaceObject> satellites = null
    ) {
        Name = name;
        MeanRadiusInKM = meanRadiusInKM;
        Volume10pow9KM3 = volume10pow9KM3;
        Mass10pow21kg = mass10pow21kg;
        Density = density;
        SurfaceGravity = surfaceGravity;
        TypeOfObject = typeOfObject;
        Satellites = satellites ?? new List<SpaceObject>();
    }
}
csharp
public class SpaceObjectDataProvider {
    public List<SpaceObject> GenerateData() {
        return new List<SpaceObject>() {
            new SpaceObject("Sun", 696000, 1412000000, 1989100000, 1.409, 274.0, "Star", new List<SpaceObject>() {
                new SpaceObject("Mercury", 2439.7, 60.83, 330.2, 5.43, 3.7, "Planet"),
                new SpaceObject("Venus", 6051.8, 928.43, 4868.5, 5.24, 8.872, "Planet"),
                new SpaceObject("Earth", 6371.0, 1083.21, 5973.6, 5.515, 9.78033, "Planet", new List<SpaceObject> () {
                    new SpaceObject("Moon", 1737.1, 21.958, 73.5, 3.3464, 1.625, "Satellite")
                }),
                new SpaceObject("Mars", 3390.0, 163.18, 641.85, 3.94, 3.7, "Planet"),
                new SpaceObject("Jupiter", 69911, 1431280, 1898600, 1.33, 24.79, "Planet", new List<SpaceObject>() {
                    new SpaceObject("Ganymede", 2631.2, 76.30, 148.2, 1.936, 1.428, "Satellite"),
                    new SpaceObject("Callisto", 2410.3, 58.65, 107.6, 1.83, 1.23603, "Satellite"),
                    new SpaceObject("Io", 1821.5, 25.32, 89.3, 3.528, 1.797, "Satellite"),
                    new SpaceObject("Europa", 1561, 15.93, 48, 3.01, 1.316, "Satellite"),
                }),
                new SpaceObject("Saturn", 58232, 827130, 568460, 0.70, 10.445, "Planet", new List<SpaceObject>() {
                    new SpaceObject("Titan", 2576, 71.52, 134.5, 1.88, 1.354, "Satellite"),
                    new SpaceObject("Rhea", 764.4, 1.87, 2.3166, 1.23, 0.26, "Satellite"),
                    new SpaceObject("Iapetus", 736, 1.55, 1.9739, 1.08, 0.223, "Satellite"),
                    new SpaceObject("Dione", 561.6, 0.73, 1.096, 1.48, 0.232, "Satellite"),
                    new SpaceObject("Tethys", 533, 0.624, 0.6173, 1.15, 0.145, "Satellite"),
                    new SpaceObject("Enceladus", 252.1, 0.067, 0.108, 1.61, 0.111, "Satellite"),
                    new SpaceObject("Mimas", 198.3, 0.033, 0.03749, 1.15, 0.06363, "Satellite")
                }),
                new SpaceObject("Uranus", 25362, 68340, 86832, 1.30, 8.87, "Planet", new List<SpaceObject>() {
                    new SpaceObject("Titania", 788.9, 2.06, 3.526, 1.72, 0.378, "Satellite"),
                    new SpaceObject("Oberon", 761.4, 1.85, 3.014, 1.63, 0.347, "Satellite"),
                    new SpaceObject("Umbriel", 584.7, 0.84, 1.2, 1.4, 0.234, "Satellite"),
                    new SpaceObject("Ariel", 578.9, 0.81, 1.35, 1.67, 0.269, "Satellite"),
                    new SpaceObject("Miranda", 235.8, 0.055, 0.0659, 1.20, 0.07910375, "Satellite"),
                }),
                new SpaceObject("Neptune", 24622, 62540, 102430, 1.76, 11.15, "Planet", new List<SpaceObject>() {
                    new SpaceObject("Triton", 1353.4, 10.38, 21.5, 2.061, 0.782, "Satellite"),
                    new SpaceObject("Proteus", 210, 0.038, 0.050, 1.3, 0.0666, "Satellite"),
                }),
                new SpaceObject("Eris", 1170, 7, 16.7, 2.25, 0.662, "Dwarf planet"),
                new SpaceObject("Pluto", 1153, 7.15, 13.105, 2.0, 0.61, "Dwarf planet"),
                new SpaceObject("Makemake", 710, 1.8, 3, 2.0, 0.4, "Dwarf planet"),
                new SpaceObject("Haumea", 575, 1.3, 4.006, 3, 0.44, "Dwarf planet"),
                new SpaceObject("Ceres", 475, 0.437, 0.95, 2.08, 0.27, "Dwarf planet"),
                new SpaceObject("Pallas", 266, 0.078, 0.211, 2.8, 0.2, "Asteroid"),
                new SpaceObject("Vesta", 264.6, 0.078, 0.262, 3.42, 0.251, "Asteroid")
            })
        };
    }
}
csharp
// ...
builder.Services.AddSingleton<SpaceObjectDataProvider>();

Run Demo: TreeList - Hierarchical Data

Load Children in Code

If your data source does not include a field that stores child nodes, you can populate the TreeList component with data during component initialization. Follow the steps below to populate the component with data in code:

  1. Bind the Data parameter to a C# property.
  2. Populate this property with root data items in the OnInitialized or OnInitializedAsync lifecycle method.
  3. Handle the ChildrenLoading event. In the event handler, use the Parent event argument to determine the processed node and assign the node’s children to the Children collection.

Note

The TreeList component loads all data during the component initialization when you handle the ChildrenLoading event. Instead of this event, you can handle the ChildrenLoadingOnDemand event to load node children when the node is expanded for the first time. Refer to the following section for additional information: Load Data on Demand.

The following example populates the TreeList component with data during the component initialization:

razor
@inject SpaceObjectDataProvider SpaceObjectDataProvider

<DxTreeList Data="TreeListData" ChildrenLoading="OnChildrenLoading">
    <Columns>
        <DxTreeListDataColumn FieldName="Name" />
        <DxTreeListDataColumn FieldName="TypeOfObject" Caption="Type" />
        <DxTreeListDataColumn FieldName="Mass10pow21kg" Caption="Mass, kg" DisplayFormat="N2">
            <HeaderCaptionTemplate>Mass, 10<sup>21</sup> &#215; kg</HeaderCaptionTemplate>
        </DxTreeListDataColumn>
        <DxTreeListDataColumn FieldName="MeanRadiusInKM" Caption="Radius, km" DisplayFormat="N2"/>
        <DxTreeListDataColumn FieldName="Volume10pow9KM3" DisplayFormat="N2">
            <HeaderCaptionTemplate>Volume, 10<sup>9</sup> &#215; km<sup>3</sup></HeaderCaptionTemplate>
        </DxTreeListDataColumn>
        <DxTreeListDataColumn FieldName="SurfaceGravity" DisplayFormat="N2">
            <HeaderCaptionTemplate>Gravity, m/s<sup>2</sup></HeaderCaptionTemplate>
        </DxTreeListDataColumn>
    </Columns>
</DxTreeList>

@code {
    object TreeListData { get; set; }

    protected override async Task OnInitializedAsync() {
        TreeListData = SpaceObjectDataProvider.GenerateRootData();
    }

    void OnChildrenLoading(TreeListChildrenLoadingEventArgs e) {
        e.Children = SpaceObjectDataProvider.GetChildren((SpaceObject)e.Parent);
    }
}
csharp
using System.Collections.Generic;

public class SpaceObject {
    public string Name { get; set; }
    public double MeanRadiusInKM { get; set; }
    public double Volume10pow9KM3 { get; set; }
    public double Mass10pow21kg { get; set; }
    public double Density { get; set; }
    public double SurfaceGravity { get; set; }
    public string TypeOfObject { get; set; }
    public List<SpaceObject> Satellites { get; set; }
    public SpaceObject(
        string name,
        double meanRadiusInKM,
        double volume10pow9KM3,
        double mass10pow21kg,
        double density,
        double surfaceGravity,
        string typeOfObject,
        List<SpaceObject> satellites = null
    ) {
        Name = name;
        MeanRadiusInKM = meanRadiusInKM;
        Volume10pow9KM3 = volume10pow9KM3;
        Mass10pow21kg = mass10pow21kg;
        Density = density;
        SurfaceGravity = surfaceGravity;
        TypeOfObject = typeOfObject;
        Satellites = satellites ?? new List<SpaceObject>();
    }
}
csharp
public class SpaceObjectDataProvider {

    public List<SpaceObject> GetChildren(SpaceObject spaceObject) {
        switch(spaceObject.Name) {
            case "Sun":
                return new List<SpaceObject>() {
                    new SpaceObject("Mercury", 2439.7, 60.83, 330.2, 5.43, 3.7, "Planet"),
                    new SpaceObject("Venus", 6051.8, 928.43, 4868.5, 5.24, 8.872, "Planet"),
                    new SpaceObject("Earth", 6371.0, 1083.21, 5973.6, 5.515, 9.78033, "Planet"),
                    new SpaceObject("Mars", 3390.0, 163.18, 641.85, 3.94, 3.7, "Planet"),
                    new SpaceObject("Jupiter", 69911, 1431280, 1898600, 1.33, 24.79, "Planet"),
                    new SpaceObject("Saturn", 58232, 827130, 568460, 0.70, 10.445, "Planet"),
                    new SpaceObject("Uranus", 25362, 68340, 86832, 1.30, 8.87, "Planet"),
                    new SpaceObject("Neptune", 24622, 62540, 102430, 1.76, 11.15, "Planet"),
                    new SpaceObject("Eris", 1170, 7, 16.7, 2.25, 0.662, "Dwarf planet"),
                    new SpaceObject("Pluto", 1153, 7.15, 13.105, 2.0, 0.61, "Dwarf planet"),
                    new SpaceObject("Makemake", 710, 1.8, 3, 2.0, 0.4, "Dwarf planet"),
                    new SpaceObject("Haumea", 575, 1.3, 4.006, 3, 0.44, "Dwarf planet"),
                    new SpaceObject("Ceres", 475, 0.437, 0.95, 2.08, 0.27, "Dwarf planet"),
                    new SpaceObject("Pallas", 266, 0.078, 0.211, 2.8, 0.2, "Asteroid"),
                    new SpaceObject("Vesta", 264.6, 0.078, 0.262, 3.42, 0.251, "Asteroid")
                };
            case "Earth":
                return new List<SpaceObject>() {
                    new SpaceObject("Moon", 1737.1, 21.958, 73.5, 3.3464, 1.625, "Satellite")
                };
            case "Jupiter":
                return new List<SpaceObject>() {
                    new SpaceObject("Ganymede", 2631.2, 76.30, 148.2, 1.936, 1.428, "Satellite"),
                    new SpaceObject("Callisto", 2410.3, 58.65, 107.6, 1.83, 1.23603, "Satellite"),
                    new SpaceObject("Io", 1821.5, 25.32, 89.3, 3.528, 1.797, "Satellite"),
                    new SpaceObject("Europa", 1561, 15.93, 48, 3.01, 1.316, "Satellite")
                };
            case "Saturn":
                return new List<SpaceObject>() {
                    new SpaceObject("Titan", 2576, 71.52, 134.5, 1.88, 1.354, "Satellite"),
                    new SpaceObject("Rhea", 764.4, 1.87, 2.3166, 1.23, 0.26, "Satellite"),
                    new SpaceObject("Iapetus", 736, 1.55, 1.9739, 1.08, 0.223, "Satellite"),
                    new SpaceObject("Dione", 561.6, 0.73, 1.096, 1.48, 0.232, "Satellite"),
                    new SpaceObject("Tethys", 533, 0.624, 0.6173, 1.15, 0.145, "Satellite"),
                    new SpaceObject("Enceladus", 252.1, 0.067, 0.108, 1.61, 0.111, "Satellite"),
                    new SpaceObject("Mimas", 198.3, 0.033, 0.03749, 1.15, 0.06363, "Satellite")
                };
            case "Uranus":
                return new List<SpaceObject>() {
                    new SpaceObject("Titania", 788.9, 2.06, 3.526, 1.72, 0.378, "Satellite"),
                    new SpaceObject("Oberon", 761.4, 1.85, 3.014, 1.63, 0.347, "Satellite"),
                    new SpaceObject("Umbriel", 584.7, 0.84, 1.2, 1.4, 0.234, "Satellite"),
                    new SpaceObject("Ariel", 578.9, 0.81, 1.35, 1.67, 0.269, "Satellite"),
                    new SpaceObject("Miranda", 235.8, 0.055, 0.0659, 1.20, 0.07910375, "Satellite")
                };
            case "Neptune":
                return new List<SpaceObject>() {
                    new SpaceObject("Triton", 1353.4, 10.38, 21.5, 2.061, 0.782, "Satellite"),
                    new SpaceObject("Proteus", 210, 0.038, 0.050, 1.3, 0.0666, "Satellite")
                };
            default:
                return new List<SpaceObject>();
        }
    }

    public List<SpaceObject> GenerateRootData() {
        return new List<SpaceObject>() {
            new SpaceObject("Sun", 696000, 1412000000, 1989100000, 1.409, 274.0, "Star")
        };
    }
}
csharp
// ...
builder.Services.AddSingleton<SpaceObjectDataProvider>();

Observable Data Collections

You can bind the Blazor TreeList to a data collection that implements the INotifyCollectionChanged or IBindingList interface. For instance, you can use the standard ObservableCollection<T> or BindingList<T> objects. These collections notify the TreeList about relevant changes (when items are added or removed, when the entire collection is refreshed, and so on). The TreeList listens to these notifications and updates its data automatically.

To enable automatic data updates for individual cells, the item object should also implement the INotifyPropertyChanged interface.

Note

After you bind a dynamic data collection to the TreeList component, the collection sends notifications after each change. When you make a sequence of modifications (for instance, in a for loop), the TreeList is re-rendered on each notification.

Based on the collection’s structure, follow the steps described in the Flat Data or Hierarchical Data section to bind the TreeList component to this collection. The following code sample binds the component to an ObservableCollection that has a flat structure. The Update Progress button changes Status field values:

razor
@using Observable.Services
@using System.Collections.ObjectModel
@inject EmployeeTaskService EmployeeTaskService

<DxTreeList Data="TreeListData" KeyFieldName="Id" ParentKeyFieldName="ParentId">
    <Columns>
        <DxTreeListDataColumn FieldName="Name" Caption="Task" />
        <DxTreeListDataColumn FieldName="EmployeeName" />
        <DxTreeListDataColumn FieldName="StartDate" />
        <DxTreeListDataColumn FieldName="DueDate" />
        <DxTreeListDataColumn FieldName="Status" Caption="Progress" DisplayFormat="p0" />
    </Columns>
</DxTreeList>

<DxButton Click="OnClick" Text="Update Progress" />

@code {
    ObservableCollection<EmployeeTask> TreeListData { get; set; }
    Random fixRand;

    protected override void OnInitialized() {
        TreeListData = new ObservableCollection<EmployeeTask>(EmployeeTaskService.GenerateData());
        fixRand = new Random();
    }

    void OnClick() {
        foreach (EmployeeTask task in TreeListData) {
            if(task.Status != 1) {
                double change = fixRand.NextDouble();
                task.Status = (task.Status + change > 1) ? 1 : task.Status + change;
            }
        }
    }
}
csharp
public class EmployeeTask : INotifyPropertyChanged {
    public EmployeeTask(
        int id,
        int parentId,
        string name,
        string employeeName,
        DateTime startDate,
        DateTime dueDate,
        double status
    ) {
        Id = id;
        ParentId = parentId;
        Name = name;
        EmployeeName = employeeName;
        StartDate = startDate;
        DueDate = dueDate;
        Status = status;
     }

    public int _id;
    public int _parentId;
    public string _name;
    public string _employeeName;
    public DateTime _startDate;
    public DateTime _dueDate;
    double _status;

    public int Id {
        get => _id;
        set => SetPropertyValue(ref _id, value);
    }       
    public int ParentId {
        get => _parentId;
        set => SetPropertyValue(ref _parentId, value);
    }   
    public string Name {
        get => _name;
        set => SetPropertyValue(ref _name, value);
    }
    public string EmployeeName {
        get => _employeeName;
        set => SetPropertyValue(ref _employeeName, value);
    }
    public DateTime StartDate {
        get => _startDate;
        set => SetPropertyValue(ref _startDate, value);
    }
    public DateTime DueDate {
        get => _dueDate;
        set => SetPropertyValue(ref _dueDate, value);
    }
    public double Status {
        get => _status;
        set => SetPropertyValue(ref _status, value);
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void SetPropertyValue<T>(ref T property, T value, [CallerMemberName] string propertyName = "") {
        if (EqualityComparer<T>.Default.Equals(property, value))
            return;
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
csharp
public class EmployeeTaskService {
    public List<EmployeeTask> GenerateData() {
        return new List<EmployeeTask>() {
            new EmployeeTask(1, 0, "Simplify & Clarify Product Messaging", "John Heart", new DateTime(2018, 4, 3), new DateTime(2018, 4, 14), 14),
            new EmployeeTask(2, 1, "Prepare Financial Reports", "Samantha Bright", new DateTime(2018, 4, 3), new DateTime(2018, 4, 7), 17),
            new EmployeeTask(3, 1, "Prepare Marketing Plan", "Arthur Miller", new DateTime(2018, 4, 7), new DateTime(2018, 4, 14), 11),
            new EmployeeTask(4, 0, "Create Action Plan to Improve Customer Engagement", "Robert Reagan", new DateTime(2017, 8, 8), new DateTime(2018, 4, 8), 23),
            new EmployeeTask(5, 4, "Update Personnel Files", "Greta Sims", new DateTime(2017, 8, 8), new DateTime(2017, 10, 18), 100),
            new EmployeeTask(6, 4, "Review Health Insurance Options", "Brett Wade", new DateTime(2017, 9, 27), new DateTime(2017, 11, 10), 37),
            new EmployeeTask(7, 4, "Choose Between PPO and HMO Health Plan", "Sandra Johnson", new DateTime(2017, 12, 13), new DateTime(2018, 3, 23), 17),
            new EmployeeTask(8, 4, "Update Google Adwords Strategy", "Ed Holmes", new DateTime(2017, 8, 23), new DateTime(2017, 12, 23), 45),
            new EmployeeTask(9, 4, "Create New Brochure Design", "Barb Banks", new DateTime(2018, 1, 3), new DateTime(2018, 3, 14), 17),
            new EmployeeTask(10, 4, "Obtain Price Quote for New Brochure", "Kevin Carter", new DateTime(2018, 2, 1), new DateTime(2018, 3, 15), 18),
            new EmployeeTask(11, 4, "Brochure Design Review", "Cindy Stanwick", new DateTime(2017, 8, 22), new DateTime(2017, 10, 28), 4),
            new EmployeeTask(12, 4, "Review Website Redesign Strategy", "Sammy Hill", new DateTime(2017, 9, 16), new DateTime(2018, 3, 6), 73),
            new EmployeeTask(13, 4, "Roll Out New Website", "Davey Jones", new DateTime(2017, 11, 7), new DateTime(2018, 2, 6), 9),
            new EmployeeTask(14, 4, "Update Sales/Marketing Strategy", "Victor Norris", new DateTime(2017, 12, 13), new DateTime(2018, 4, 2), 5),
            new EmployeeTask(15, 4, "Update Sales/Revenue Report", "Mary Stern", new DateTime(2017, 12, 25), new DateTime(2018, 4, 2), 4),
            new EmployeeTask(16, 4, "Direct vs Online Sales Comparison Report", "Robin Cosworth", new DateTime(2018, 1, 2), new DateTime(2018, 3, 20), 14),
            new EmployeeTask(17, 4, "Review Sales Report and Approve Modifications", "Kelly Rodriguez", new DateTime(2017, 9, 4), new DateTime(2017, 10, 30), 8),
            new EmployeeTask(18, 4, "Update R&D Strategy", "James Anderson", new DateTime(2017, 11, 13), new DateTime(2017, 12, 4), 12),
            new EmployeeTask(19, 4, "Discuss Updated R&D Strategy", "Antony Remmen", new DateTime(2017, 10, 29), new DateTime(2017, 12, 31), 14),
            new EmployeeTask(20, 4, "Update QA Strategy", "Olivia Peyton", new DateTime(2017, 10, 31), new DateTime(2017, 11, 2), 18),
            new EmployeeTask(21, 4, "Schedule Training Events", "Taylor Riley", new DateTime(2017, 11, 19), new DateTime(2018, 4, 7), 21),
            new EmployeeTask(22, 4, "Approve Hiring of John Jeffers", "Amelia Harper", new DateTime(2018, 1, 7), new DateTime(2018, 4, 8), 10),
            new EmployeeTask(23, 0, "Increase Average Subscription Price", "Wally Hobbs", new DateTime(2017, 8, 9), new DateTime(2017, 9, 13), 96),
            new EmployeeTask(24, 23, "Update Non-Compete Agreements", "Brad Jameson", new DateTime(2017, 8, 9), new DateTime(2017, 9, 3), 96),
            new EmployeeTask(25, 23, "Update Employee Records with New NDA", "Karen Goodson", new DateTime(2017, 8, 23), new DateTime(2018, 9, 10), 100)
        };
    }
}
csharp
// ...
builder.Services.AddSingleton<EmployeeTaskService>();

Run Demo: TreeList - Observable Data Collections

Server-Side Data

You can use the GridDevExtremeDataSource<T> to bind the TreeList to a large flat dataset that implements the IQueryable interface. When you use this data source, the TreeList component performs the following actions to optimize performance and reduce memory consumption:

  • Initially loads only root nodes and retrieves child nodes when a user expands a parent node for the first time.
  • Delegates data filtering operations to the underlying query provider (such as LINQ to Objects, EF Core, and so on).
  • If you do not specify the FilterTreeMode property value, the component operates in Nodes mode. In this mode, the TreeList ignores parent-child relationships and displays all nodes that meet the filter criteria at one level.

To build a hierarchical structure of TreeList nodes, specify additional properties that define node relationship:

KeyFieldNameA field name that contains node unique identifiers.ParentKeyFieldName

A field name that contains a node’s parent identifier.

For root nodes, this field’s value is null, 0, Guid.Empty, or matches the RootValue. The TreeList component does not display a node if its parent identifier does not point to any node.

HasChildrenFieldNameA field name that returns whether a node has children. The component uses this property to determine which nodes require expand buttons.

Note

Data types of RootValue and data source fields assigned to KeyFieldName and ParentKeyFieldName properties should match.

Data source implementation is based on the DevExpress DevExtreme.AspNet.Data library.

Local Queryable Collections

Follow the steps below to bind the TreeList to a large flat dataset stored locally:

  1. Create a GridDevExtremeDataSource instance and pass your IQueryable data collection as the constructor parameter.
  2. Assign this instance to the TreeList’s Data property in the OnInitialized or OnInitializedAsync lifecycle method.
  3. Specify KeyFieldName, ParentKeyFieldName, and HasChildrenFieldName properties.
  4. Optional. Specify the RootValue property if the parent identifier of root nodes differs from 0, null, and Guid.Empty.

The following code sample example uses the Entity Framework Core data access technology to bind the TreeList component to an IQueryable<T> data collection:

razor
@inject CitiesService CitiesService

<DxTreeList Data="@Data"
            KeyFieldName="ID" 
            ParentKeyFieldName="ParentID" 
            HasChildrenFieldName="HasChildren">
    <Columns>
        <DxTreeListDataColumn Caption="Location" FieldName="Name" />
        <DxTreeListDataColumn FieldName="CityType" />
        <DxTreeListDataColumn FieldName="Year" DisplayFormat="d"/>
        <DxTreeListDataColumn FieldName="RecordType" />
        <DxTreeListDataColumn FieldName="Population" />
    </Columns>
</DxTreeList> 

@code {
    object Data { get; set; }

    protected override async Task OnInitializedAsync() {
        var cities = await CitiesService.GetCitiesAsync();
        Data = new GridDevExtremeDataSource<Location>(cities.AsQueryable());
    }
}
csharp
public class Location {
    public string? Name { get; set; }
    public string? City { get; set; }
    public string? Country { get; set; }
    public decimal Population { get; set; }
    public int ID { get; set; }
    public int ParentID { get; set; }
    public string? CityType { get; set; }
    public string? RecordType { get; set; }
    public int? Year { get; set; }
    public bool HasChildren { get; set; }
}
csharp
public class CitiesService {
    CitiesContext _context;
    public CitiesService(IDbContextFactory<CitiesContext> contextFactory) {
        _context = contextFactory.CreateDbContext();
    }
    public void SeedData() {
        _context.Database.EnsureCreated();
        if (_context.Locations.Any())
            return;
        var stream = File.OpenRead(@"cities.json");
        var cities = System.Text.Json.JsonSerializer.Deserialize<Location[]>(stream);
        if (cities == null)
            return;
        foreach (var city in cities) {
            city.ID++;
            city.ParentID++;
        }
        _context.Locations.AddRange(cities);
        _context.SaveChanges();
    }

    public async Task<Location[]?> GetCitiesAsync() {
        await _context.Locations.LoadAsync();
        return _context.Locations.Local.ToArray();
    }
}

Run Demo: TreeList - Large DatasetView Example: Bind Blazor TreeList to DevExtreme data source with Entity Framework Core

Queryable Collections as HTTP Services

Follow these steps to bind the TreeList to a large data collection published as an HTTP service:

  1. Create a GridDevExtremeDataSource class instance and pass two constructor parameters:

  2. Assign the GridDevExtremeDataSource class instance to the TreeList’s Data property in the OnInitialized or OnInitializedAsync lifecycle method.

  3. On the service side, implement an API controller. Create action methods that use the DevExtreme.AspNet.Data library’s DataSourceLoader class to create a LoadResult object based on load options.

  4. Specify KeyFieldName, ParentKeyFieldName, and HasChildrenFieldName properties.

  5. Optional. Specify the RootValue property if the parent identifier of root nodes differs from 0, null, and Guid.Empty.

razor
@inject HttpClient HttpClient

<DxTreeList Data="Data"
            KeyFieldName="Id"
            ParentKeyFieldName="ParentId"
            HasChildrenFieldName="HasChildren">
    <Columns>
        <DxTreeListDataColumn FieldName="Name" Caption="Task"/>
        <DxTreeListDataColumn FieldName="EmployeeName" Caption="Assigned To"/>
        <DxTreeListDataColumn FieldName="StartDate" />
        <DxTreeListDataColumn FieldName="DueDate" />
        <DxTreeListDataColumn FieldName="Priority" />
        <DxTreeListDataColumn Caption="Progress" FieldName="Status" />
    </Columns>
</DxTreeList>

@code {
    object Data { get; set; }

    protected override async Task OnInitializedAsync() {
        var uri = new Uri("https://js.devexpress.com/Demos/NetCore/api/TreeListWebApi/EmployeeTasks");
        Data = new GridDevExtremeDataSource<EmployeeTask>(HttpClient, uri);
    }
}
csharp
// ...
builder.Services.AddHttpClient();
csharp
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
// ...

[Route("api /[controller] /[action]")]
public class TreeListWebApiController : Controller
{
    NorthwindContext _nwind;

    [HttpGet]
    public object EmployeeTasks(DataSourceLoadOptions loadOptions) {
        return DataSourceLoader.Load(_nwind.EmployeeTasks, loadOptions);
    }
    // ...
}

Run Demo: TreeList - Large Dataset via HTTP Service

Custom HTTP Requests

You can also generate and send custom HTTP requests:

  1. Implement an asynchronous function that returns a Task<Stream> object and accepts two parameters:

  2. Create a GridDevExtremeDataSource class instance and pass two constructor parameters (the newly created function and the URL to the service’s controller action).

  3. Assign the GridDevExtremeDataSource instance to the TreeList’s Data property in the OnInitialized or OnInitializedAsync lifecycle method.

  4. Specify KeyFieldName, ParentKeyFieldName, and HasChildrenFieldName properties.

  5. Optional. Specify the RootValue property if the parent identifier of root nodes differs from 0, null, and Guid.Empty.

The following code snippet adds an authorization header to HTTP requests. Note that you should process authorization information in the service’s controller.

razor
@*...*@

@code {
    // ...

    protected override async Task OnInitializedAsync() {
        var uri = new Uri("https://js.devexpress.com/Demos/NetCore/api/TreeListWebApi/EmployeeTasks");
        Data = new GridDevExtremeBasedDataSource<EmployeeTask>(ExecuteDataSourceHttpRequest, uri);
    }

    async Task<Stream> ExecuteDataSourceHttpRequest(Uri uri, CancellationToken cancellationToken) {
        using var request = new HttpRequestMessage(HttpMethod.Get, uri);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "...");
        var response = await HttpClient.SendAsync(request);
        return await response.Content.ReadAsStreamAsync();
    }
}

Limitations

The GridDevExtremeDataSource imposes the following limitations on TreeList features:

Load Data on Demand

DevExpress Blazor TreeList can initially load only root nodes and retrieve the node’s children when the node is expanded for the first time. This technique optimizes performance and reduces overall memory consumption when bound to a large dataset. Follow the steps below to enable on demand mode in the TreeList component:

  1. Bind the Data parameter to a C# property.
  2. Populate this property with root data items in the OnInitialized or OnInitializedAsync lifecycle method.
  3. Specify the HasChildrenFieldName property. The component uses this property to determine which nodes require expand buttons.
  4. Handle the ChildrenLoadingOnDemand event. In the event handler, use the Parent event argument to determine the processed node and assign the node’s children to the Children collection.

In the following example, the TreeList component displays the file structure:

razor
@inject IFileSystemDataProvider FileSystemDataProvider

<DxTreeList Data="TreeListData"
            HasChildrenFieldName="HasChildren"
            ChildrenLoadingOnDemand="TreeList_ChildrenLoadingOnDemand"
            CustomizeCellDisplayText="TreeList_CustomizeCellDisplayText">
    <Columns>
        <DxTreeListDataColumn FieldName="Name" />
        <DxTreeListDataColumn FieldName="Type" Width="100" />
        <DxTreeListDataColumn FieldName="DateModified" Width="120" />
        <DxTreeListDataColumn FieldName="Size" Width="100" />
    </Columns>
</DxTreeList>

@code {
    object TreeListData { get; set; }

    protected override async Task OnInitializedAsync() {
        TreeListData = await FileSystemDataProvider.GetRootItemsAsync();
    }

    Task TreeList_ChildrenLoadingOnDemand(TreeListChildrenLoadingOnDemandEventArgs e) {
        var item = e.Parent as FileSystemDataItem;
        e.Children = item.Children;
        return Task.CompletedTask;
    }

    void TreeList_CustomizeCellDisplayText(TreeListCustomizeCellDisplayTextEventArgs e) {
        if(e.FieldName == "Size") {
            var item = (FileSystemDataItem)e.DataItem;
            e.DisplayText = GetSizeColumnDisplayText(item.Type, item.Size);
        }
    }

    string GetSizeColumnDisplayText(FileType fileType, long size) {
        if(fileType == FileType.Folder)
            return null;
        if(size >= 1024)
            return string.Format("{0:### ### ###} KB", size / 1024);
        return string.Format("{0} Bytes", size);
    }
}
csharp
using System.Collections.Generic;

public class FileSystemDataItem {
    public string Name { get; set; }
    public FileType Type { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateModified { get; set; }
    public long Size { get; set; }

    public List<FileSystemDataItem> Children { get; set; }
    public bool HasChildren => Children != null && Children.Count > 0;
}

public enum FileType { File, Folder }
csharp
public class FileSystemDataProvider {
    List<FileSystemDataItem> _rootItems;

    public FileSystemDataProvider(DataSourcesFileContentProvider fileContentProvider) {
        FileContentProvider = fileContentProvider;
    }

    DataSourcesFileContentProvider FileContentProvider { get; }

    public async Task<List<FileSystemDataItem>> GetRootItemsAsync() {
        if(_rootItems == null) {
            var json = await FileContentProvider.GetFileSystemDataItemsContentAsync();
            _rootItems = JsonSerializer.Deserialize<List<FileSystemDataItem>>(json);
        }
        return _rootItems;
    }
}

public class DataSourcesFileContentProvider{
    string _fileSystemDataItemsContent;

    public async Task<string> GetFileSystemDataItemsContentAsync() {
        if(_fileSystemDataItemsContent == null) {
            string pathToDataFile = Path.Combine(AppContext.BaseDirectory, "DataSources", "FileSystemDataItems.json");
            _fileSystemDataItemsContent = await File.ReadAllTextAsync(pathToDataFile);
        }
        return _fileSystemDataItemsContent;
    }
}
csharp
// ...
builder.Services.AddSingleton<FileSystemDataProvider>();
builder.Services.AddSingleton<DataSourcesFileContentProvider>();

Run Demo: TreeList - Load on Demand

Limitations

The on demand data loading mode has the following specifics and limitations: