Sunday, May 5, 2013

How to display a recursive tree structure with ASP.NET MVC

Synopsis:

This article show how to display a recursive tree structure in ASP.NET MVC with Razor using @helper


Tree structures are ubiquitous in programming. Therefore the task of displaying them in a web application occurs quite often as well.

Component-based frameworks, such as ASP.NET Webforms, usually provide specialized tree-components for this purpose. When using a template engine like Razor in ASP.NET MVC you have to code them "by hand". This article shows an easy way how to do that.

Data structure

Let's first have a look at our model: a tree is a classical example of a recursive data structur. Each Element stores a reference to at least it's parent or to a collection of child elements (or both). These references are again of type Element, hence the recursive nature of the data structure. You'll also may want to have some sort of Index property to define the ordering of subelements within one parent element.

When the data is comes from a relational database you'll typically have a parent reference, so we will go along with this case in our example. Let's say our Element looks like

class Element {
  public string Title { get; set; }
  public Element Parent { get; set; }
  public int Index { get; set; }
}

In order to have some sample data we construct a tree using

private IEnumerable<Element> CreateTree() {
  var root = new Element { Title="Root", Index=1 };
  var element1 = new Element { Title="Element 1", Index=1, Parent=root };
  var element2 = new Element { Title="Element 2", Index=2, Parent=root };
  var element3 = new Element { Title="Element 3", Index=3, Parent=root };
  var element11 = new Element { Title="Element 1.1", Index=1, Parent=element1 };
  var element12 = new Element { Title="Element 1.2", Index=2, Parent=element1 };
  var element121 = new Element { Title="Element 1.2.1", Index=1, Parent=element12 };
  var element21 = new Element { Title="Element 2.1", Index=1, Parent=element2 };
  var element22 = new Element { Title="Element 2.2", Index=2, Parent=element2 };
  var element31 = new Element {Title="Element 3.1", Index=1, Parent=element3 };
  return new List {root, element1, element2, element3, element11, 
                   element12, element121, element21, element22, element31};
}

This results in a structure like

  • Root
    • Element 1
      • Element 1.1
      • Element 1.2
        • Element 1.2.1
    • Element 2
      • Element 2.1
      • Element 2.2
    • Element 3
      • Element 3.1

Passing the data into the template


The data structure is passed into the (strongly typed) template via the Model property. For this purpose, we define the model type in our template with

@model IEnumerable<Element>

And pass it as parameter when instantiating our View within the controller

  ...
  return View(CreateTree());
  ...

Getting the root and children of an element


As you have seen we do not explicitely mention the root of our tree when passing the data to the View. As well we have no property for getting the children of an element. This is often the case when you get your data from a single table of a relational database.

Both informations can easily be computed using LINQ queries: for getting the root of our tree we simply look for the element having no parent using

var rootElements = Model.Where(e=>e.Parent == null).OrderBy(e => e.Index);

This way we also can display trees with "multiple roots" (which are mutiple trees in fact).

The children of an element can similarily be computed using

var children = Model.Where(e=>e.Parent == element).OrderBy(e => e.Index);

One can object that this approach is not the most efficient: for each list subelements you have to scan the whole list. If you're processing very large trees this might be an issue. In this case you should preprocess the tree and construct the set of children of each element explicitely. For small trees the costs for scanning the whole list are usually much smaller than the time taken for database query and can be neglected.

The display function


Now we want to display our tree as HTML, i.e. we want to produce the following output using a Razor template:

<ul>
  <li>Element 1
  <ul>
  <li>Element 1.1</li>
  <li>Element 1.2     
  <ul>
    <li>Element 1.2.1</li>
  </ul>
  ... 
</ul>

Since trees are inherently recursive they are usually also processed using recursive functions. In general one speaks of transforming trees, in this case we transform our data tree into an HTML tree.

A function to write the HTML for our structure (in pseudocode) would look like

  function DisplayTree(elementList):
    write "<ul>"
    for each element in elementList do:
      write "<li>"
      write element title
      if element has children:
        call DisplayTree(children of element)
      write "</li>"
    end for loop
    write "</ul>"
  end function

At the top level (i.e. within our template) this function would be called with a collection containing only our root element.

Razor's @helper function


Now we have to decide where and how to write our DisplayTree function. One way would be to write an extension method for HTMLHelper (e.g. in the Helper directory) and use it in our .cshtml template like

@Html.DisplayTree(elements)

This approach is used for displaying form controls within ASP.NET MVC. Although this would perfectly work, it somehow breaks the separation between view-related "non-visual" code in my opinion: when following the ASP.NET MVC pattern, all HTML-producing parts of the program are written in Razor (i.e. in .cshtml files). When you want to change the appearance of the tree (say add some CSS classes to the list elements) you'd have to find the code in the Helper directory and modify plain C# code.

Luckily there's a better way: using Razor's @helper directive you can write functions (and also recursive ones) right in the template file. An implementation of our Displaytree function in Razor would look like:

@helper DisplayTree(IEnumerable elements) {
  <ul>
  @foreach (var element in elements) {
    <li>@element.Title
    @{
      var children = Model.Where(e => e.Parent == element).OrderBy(e=>e.Index);
      if (children.Count() > 0) {
        @DisplayTree(children)
      }
    }
    </li>
  }
  </ul>
}

To actually display the tree in your page, just put a

...
@DisplayTree(Model.Where(e=>e.Parent==null).OrderBy(e=>e.Index))
...

Example project


You can download an example project which contains the complete source for this article. Note that you will need to download the Nuget packages before running the solution. VS2012 does this automatically when building.

5 comments:

  1. Nothing is showing after running the project

    ReplyDelete
  2. Hi Vips,
    I think you didnt put the below line before that helper function
    "@DisplayTree(Model.Where(e=>e.Parent==null).OrderBy(e=>e.Index))"

    ReplyDelete
  3. Martin this is great, I was looking to display a tree structure in a report, and your solution work perfect.
    Thank you,

    ReplyDelete
  4. Martin well done, this really solved my headache. thanks

    ReplyDelete