Rendering a tree view using ASP.NET MVC 3 + Razor

I modified the Html Helper created by Mike Hadlow based on the answered provided by marcind over on Stack Overflow and it works pretty beautifully.

I also made a couple “improvements”:

  1. Updated to use the HtmlTextWriter provided by the HtmlHelper so that it is compatible with MVC 3.
  2. Added a children expression so you don’t need the IComposite<T> interface
  3. Added a URL expression so you can specify a URL for the content of the <li> element

I wanted to use generated classes as the source so I didn’t feel like implementing the IComposite<T> interface and breaking the code generation (or investing the time to alter the code generation just to implement this custom interface). Therefore, I added the ability to specify an expression that returns a generic list List<T>.

Here is what the updated Razor syntax looks like:

<div id="treeView">
    @Html.RenderTree(Model.Divisions, division => division.Name, division => division.Children.ToList(), division => "../../Division/Detail/" + division.Id)
</div>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Here is my updated extension method:

    public static class TreeRenderHtmlHelper
    {
        public static string RenderTree<T>(this HtmlHelper htmlHelper, List<T> rootLocations, Func<T, string> locationRenderer, Func<T, List<T>> childrenRenderer, Func<T, string> urlRenderer)
        {
            return new TreeRenderer<T>(rootLocations, locationRenderer, childrenRenderer, urlRenderer).Render(new HtmlTextWriter(htmlHelper.ViewContext.Writer));
        }
    }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Here is my updated TreeRenderer<T>:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

    public class TreeRenderer<T>
    {
        private readonly Func<T, string> locationRenderer;
        private readonly Func<T, string> urlRenderer;
        private readonly Func<T, List<T>> childrenRenderer;
        private readonly List<T> rootLocations;
        private HtmlTextWriter writer;

        public TreeRenderer(List<T> rootLocations, Func<T, string> locationRenderer, Func<T, List<T>> childrenRenderer, Func<T, string> urlRenderer)
        {
            this.rootLocations = rootLocations;
            this.locationRenderer = locationRenderer;
            this.childrenRenderer = childrenRenderer;
            this.urlRenderer = urlRenderer;
        }
        public string Render(HtmlTextWriter writer)
        {
            this.writer = writer;
            RenderLocations(rootLocations);
            //var mvcString = new HtmlString();
            return string.Empty;
        }
        /// <summary>
        /// Recursively walks the location tree outputting it as hierarchical UL/LI elements
        /// </summary>
        /// <param name="locations"></param>
        private void RenderLocations(List<T> locations)
        {
            if (locations == null) return;
            if (locations.Count() == 0) return;

            InUl(() => locations.ForEach(location => InLi(() =>
            {
                writer.AddAttribute("href", urlRenderer(location));
                writer.RenderBeginTag(HtmlTextWriterTag.A);
                writer.Write(locationRenderer(location));

                writer.RenderEndTag();

                var children = this.childrenRenderer(location);

                RenderLocations(children);
            })));
        }
        private void InUl(Action action)
        {
            writer.WriteLine();
            writer.RenderBeginTag(HtmlTextWriterTag.Ul);
            action();
            writer.RenderEndTag();
            writer.WriteLine();
        }
        private void InLi(Action action)
        {
            writer.RenderBeginTag(HtmlTextWriterTag.Li);
            action();
            writer.RenderEndTag();
            writer.WriteLine();
        }
    }
Advertisement

8 thoughts on “Rendering a tree view using ASP.NET MVC 3 + Razor

  1. I am trying to construct some dummy data in my Index method similar to what is represented on the original blog’s comment section. Any assistance you can provide me would be greatly appreciated.

    Like

  2. Can you please provide me the table that you used. Basically What I need to understand is how the model for the razor is generated, i.e. how is the parent child relationship getting established. That is why if you could provide the source code of the project, it would be great.

    Like

  3. I remember using his post and the result was html markup TEXT rendered in browser, not some elements. As far as I can tell, this code still returns simple string from a helper, not an HtmlMvcString. believe me I can post code without looking at it too, it’s just I see no point in doing so

    Like

  4. Gleb,

    I encountered the same issue, which as you can see from my code I return string.Empty. rather than what Milke returned which was writer.InnerWriter.ToString(). Check out his Render() method and compare it to mine.

    Like

  5. Thank you for the learning experience. After noodling through your code and having Stack Overflow issues I gave up and relooked at Matt Hidinger’s NuGet install-package MvcTreeView. Along with jQuery’s TreeView it’s a sweet solution that was easy to implement. I build my tree structure using SQL Server HierarchyID and use the string version for my primary key.

    Like

  6. Mark, thank you for post! I have next error, when i use RenderTree function in my view: “Argument-types for a method “LastTry.HtmlHelpers.TreeViewHelper.RenderTree (System.Web.Mvc.HtmlHelper, System.Collections.Generic.List , System.Func , System.Func <T, System.Collections.Generic.List >, System.Func ) “should not be determined for use. Try to clearly define the argument types.”. Do you khow what it means?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s