How To: Create an ASP.NET AJAX Style Folder Explorer

I recently created a folder explorer using a combination of the ASP.NET TreeView and GridView controls as well as the new ASP.NET AJAX UpdatePanel.  You can view a live demo of the folder explorer here. The code for this sample is at the bottom of the page.

[Update 7/24/2007]: Here is another post regarding this, except with a few more enhancements

[Update 7/17/2007]: Per Ruckus' comments, I would like to make the following clarifications:

  • The sample provides read-only access to the folders and files, making it more of a 'Folder Browser' instead of an Explorer
  • A full-postback is done when a folder is clicked in the right hand pane.  If you have a suggestion to a nice way to have the Folder image buttons cause async-postbacks instead of complete post-backs, let me know.  I thought about possibly handling the RowDataBound event and explicitly calling RegisterAsyncPostBackControl ...
  • Offically the TreeView control is not supposed to work inside an UpdatePanel.  I didn't run into any problem, but this is why I classified this post under 'prototype'.  It needs a little more work/investigation. 

The design of the page is pretty simple.  The left panel is a regular ASP.NET TreeView that displays the folders I am allowing the users to browse.  The right panel contains the selected folders contents - both files and folders.  Both of these controls are contained within an UpdatePanel so traversing the folder structure does not cause the complete page to refresh.

Here is the markup for the TreeView (left panel):   

<asp:TreeView ID="tvFolders" runat="server" OnSelectedNodeChanged="TvFolders_SelectedNodeChanged">
    <NodeStyle ImageUrl="Img/folder.gif" HorizontalPadding="3px" Font-Underline="false" ForeColor="black" />
    <SelectedNodeStyle Font-Underline="true" Font-Bold="true" />
</asp:TreeView> 

And here is the GridView (right panel).  The only interesting part about the GridView is that because the GridView does not display Headers when the DataSource is empty, I am using the work a round outlined here.   

<mb:GridView 
    ID="gvFolderItems" runat="server" 
    EmptyDataText="This folder is empty." 
    GridLines="None" Width="100%" BorderStyle="None" 
    OnRowDataBound="GvFolderItems_RowDataBound" 
    OnRowCommand="GvFolderItems_RowCommand" 
    AutoGenerateColumns="false" ShowHeaderWhenEmpty="true">
    <HeaderStyle HorizontalAlign="Left" BackColor="BurlyWood" />
    <Columns>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate>
                <%--The image is set when row is databound--%>
                <asp:ImageButton 
                    ID="btnItemIcon" runat="server" 
                    ImageAlign="AbsMiddle" 
                    CommandArgument='<%# Eval("Name") %>' 
                    CommandName="ItemClick" />
                <asp:LinkButton 
                    ID="btnItemName" runat="server" 
                    Text='<%# Eval("Name") %>' 
                    CommandArgument='<%# Eval("Name") %>' 
                    CommandName="ItemClick" 
                    Font-Underline="false" 
                    ForeColor="black" /> 
            </ItemTemplate>     
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Size">
            <ItemTemplate>
                <%--The image is set when row is databound--%>
                <asp:Label ID="lblSize" runat="server" />                                                
            </ItemTemplate>
        </asp:TemplateField>
   </Columns>
</mb:GridView>

Finally, because I need use the Response object to write the PDF contents to the output stream, I have to notify the UpdatePanel that any postback that originates from the GridView needs to be a full postback (instead of an partial one - you can not write to the Response object during a partial-postback.  If you do it will generate an error).  This is done by adding the PostBackTrigger item to the UpdatePanels Triggers collection and point it to our GridView.

<Triggers>
    <asp:AsyncPostBackTrigger ControlID="tvFolders" EventName="SelectedNodeChanged" />
    <asp:PostBackTrigger ControlID="gvFolderItems" />
</Triggers>  

The path to the physical folder structure I am allowing the user to browse is located at the 'rootfolder' appsetting key.  Additionally, these folders only contain PDF files so when the directory item is a file I am always displaying the PDF icon.  This logic is contained in the RowDataBound event handler and you will need to update it for each type of file your application supports.  I am binding directly to the FileSystemInfo object and displaying the Name property value for both Folders and Files and the Size property for just the Files.  You should be able to include other attributes in a similar manner.

That's It.  Enjoy! 

* Disclaimer: Offically the TreeView control is not supposed to work inside an UpdatePanel.  I didn't run into any problem, but this is why I classified this posting under 'prototype'.  It needs a little more work/investigation. 

<%@ Page Language="C#" %>
<%@ Register Assembly="MattBerseth.WebControls" Namespace="MattBerseth.WebControls" TagPrefix="mb" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <script runat="server">
    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            string rootFolder = this.Server.MapPath(ConfigurationManager.AppSettings["rootfolder"]);

            // load the treeview based on the folder strucutre
            TreeNode rootNode = new TreeNode("Root", rootFolder);
            rootNode.Expanded = true;
            rootNode.Select();
            // add the root node
            this.tvFolders.Nodes.Add(rootNode);

            // bind sub directories to the treeview
            BindDirs(rootFolder, rootNode);

            // bind the gridview to the datasource using the root node
            BindDirsContents(rootNode, this.gvFolderItems);
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    protected void GvFolderItems_RowDataBound(object sender, GridViewRowEventArgs args)
    {
        if (args.Row.RowType == DataControlRowType.DataRow)
        {
            System.IO.FileSystemInfo item = (System.IO.FileSystemInfo)args.Row.DataItem;
            ImageButton imageButton = (ImageButton)args.Row.FindControl("btnItemIcon");

            if (item is System.IO.DirectoryInfo)
            {
                imageButton.ImageUrl = @"Img/folder.gif";
            }
            else
            {
                imageButton.ImageUrl = @"Img/pdf.gif";

                System.IO.FileInfo fileInfo = (System.IO.FileInfo)item;
                Label lblSize = (Label)args.Row.FindControl("lblSize");
                lblSize.Text = string.Format("{0:N0} KB", fileInfo.Length / 1000);
            }
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    protected void GvFolderItems_RowCommand(object sender, GridViewCommandEventArgs args)
    {
        // handle either opening the item or rebinding the grid
        if (args.CommandName == "ItemClick")
        {
            string name = (string)args.CommandArgument;
            System.IO.DirectoryInfo dinfo = new System.IO.DirectoryInfo(this.tvFolders.SelectedNode.Value);

            if (System.IO.File.Exists(System.IO.Path.Combine(dinfo.FullName, name)))
            {
                System.IO.FileInfo fileInfo = new System.IO.FileInfo(System.IO.Path.Combine(dinfo.FullName, name));
                //  they clicked on a file, download it
                //  to there PC
                this.Response.Clear();
                this.Response.AddHeader("Content-Disposition", "attachment; filename=" + fileInfo.Name);
                this.Response.AddHeader("Content-Length", fileInfo.Length.ToString());  
                this.Response.ContentType = "application/octet-stream";
                this.Response.WriteFile(fileInfo.FullName);  
                this.Response.End();
            }
            else
            {
                foreach (TreeNode node in this.tvFolders.SelectedNode.ChildNodes)
                {
                    if (node.Text == name)
                    {
                        node.Selected = true;
                        node.Expanded = true;

                        // expand the parents
                        TreeNode parentNode = node.Parent;
                        while (parentNode != null)
                        {
                            parentNode.Expanded = true;
                            parentNode = parentNode.Parent;
                        }

                        // bind the gridview to the datasource
                        BindDirsContents(node, this.gvFolderItems);
                        break;
                    }
                }
            }
        }
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    protected void TvFolders_SelectedNodeChanged(object sender, EventArgs args)
    {
        BindDirsContents(this.tvFolders.SelectedNode, this.gvFolderItems);
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="node"></param>
    private static void BindDirsContents(TreeNode node, System.Web.UI.WebControls.GridView gridView)
    {
        // bind the gridview to the datasource
        gridView.DataSource = new System.IO.DirectoryInfo(node.Value).GetFileSystemInfos();
        gridView.DataBind();
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="path"></param>
    /// <param name="treeNode"></param>
    private static void BindDirs(string path, TreeNode treeNode)
    {
        if (!string.IsNullOrEmpty(path))
        {
            foreach (string directoryPath in System.IO.Directory.GetDirectories(path))
            {
                System.IO.DirectoryInfo directory = new System.IO.DirectoryInfo(directoryPath);
                TreeNode subNode = new TreeNode(directory.Name, directory.FullName);
                treeNode.ChildNodes.Add(subNode);

                // bind sub directories
                BindDirs(directoryPath, subNode);
            }
        }
    }    
    </script>
</head>
<body>
    <form id="form" runat="server">
        <asp:ScriptManager ID="scriptManager" runat="server" />
        <div>
            <p style="background-color:AliceBlue; width:700px">
            This is an example of how to combine a TreeView a GridView and an UpdatePanel to create a<br />
            nice AJAX folder browser<br />
            </p>
            <br />
            <asp:UpdatePanel ID="updPanel" runat="server">
                <Triggers>
                    <asp:AsyncPostBackTrigger ControlID="tvFolders" EventName="SelectedNodeChanged" />
                    <asp:PostBackTrigger ControlID="gvFolderItems" />
                </Triggers>                            
                <ContentTemplate>        
                    <table id="tbl" cellpadding="0px" cellspacing="0px">            
                        <tr>
                            <td style="border:solid 1px black" valign="top">
                                <div style="overflow:auto;width:300px;height:450px;">
                                    <asp:TreeView 
                                        ID="tvFolders" runat="server" 
                                        OnSelectedNodeChanged="TvFolders_SelectedNodeChanged">
                                        <NodeStyle 
                                            ImageUrl="Img/folder.gif" HorizontalPadding="3px" 
                                            Font-Underline="false" ForeColor="black" />
                                        <SelectedNodeStyle 
                                            Font-Underline="true" Font-Bold="true" />
                                    </asp:TreeView> 
                                </div>
                            </td>
                            <td style="border:solid 1px black" valign="top">
                                <div style="overflow:auto;width:400px;height:450px;">
                                    <mb:GridView 
                                        ID="gvFolderItems" runat="server" 
                                        EmptyDataText="This folder is empty." 
                                        GridLines="None" Width="100%" BorderStyle="None" 
                                        OnRowDataBound="GvFolderItems_RowDataBound" 
                                        OnRowCommand="GvFolderItems_RowCommand" 
                                        AutoGenerateColumns="false" ShowHeaderWhenEmpty="true">
                                        <HeaderStyle HorizontalAlign="Left" BackColor="BurlyWood" />
                                        <Columns>
                                            <asp:TemplateField HeaderText="Name">
                                                <ItemTemplate>
                                                    <%--The image is set when row is databound--%>
                                                    <asp:ImageButton 
                                                        ID="btnItemIcon" runat="server" 
                                                        ImageAlign="AbsMiddle" 
                                                        CommandArgument='<%# Eval("Name") %>' 
                                                        CommandName="ItemClick" />
                                                    <asp:LinkButton 
                                                        ID="btnItemName" runat="server" 
                                                        Text='<%# Eval("Name") %>' 
                                                        CommandArgument='<%# Eval("Name") %>' 
                                                        CommandName="ItemClick" 
                                                        Font-Underline="false" 
                                                        ForeColor="black" /> 
                                                </ItemTemplate>     
                                            </asp:TemplateField>
                                            <asp:TemplateField HeaderText="Size">
                                                <ItemTemplate>
                                                    <%--The image is set when row is databound--%>
                                                    <asp:Label ID="lblSize" runat="server" />                                                
                                                </ItemTemplate>
                                            </asp:TemplateField>
                                       </Columns>
                                    </mb:GridView>
                                </div>
                            </td>
                        </tr>
                    </table>
                </ContentTemplate>
            </asp:UpdatePanel>             
            <br /> 
        </div>
    </form>    
</body>
</html>


 


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/46

Listed below are links to weblogs that reference How To: Create an ASP.NET AJAX Style Folder Explorer:

» How To: Create an ASP.NET AJAX Style Folder Explorer from DotNetKicks.com
You've been kicked (a good thing) - Trackback from DotNetKicks.com [Read More]

» Create an ASP.NET AJAX Style Folder Explorer from help.net
Matt Berseth has recently created a folder explorer using a combination of the ASP.NET TreeView and GridView [Read More]

Comments


Posted by: Ruckus on July 17, 2007 12:00 AM

It looks like a folder explorer for sure, but no drag and drop, post backs on drill down, and glove on click makes if feel like a web app. There are many sites that have true explorer like functionality in the browser that this doesnt hold a candle to. Sorry.

Posted by: Dima on July 17, 2007 12:00 AM

Could you attach source code of this project.

???????

Posted by: Deep on November 19, 2007 12:00 AM

Pls send me the source code.

Posted by: Alex on November 22, 2007 12:00 AM

Hi, please send me the source code too, thank you

Posted by: mubasher on December 1, 2007 12:00 AM

nice job..can u please send me the source code.

Posted by: Sabyasachi on December 3, 2007 12:00 AM

can u please send me the source code

Posted by: david on December 9, 2007 12:00 AM

please send me the source code too thank you

Posted by: Hussnain Raza on January 8, 2008 12:00 AM

Hi Matt your articles are really very help ful for me
here i need some assistancy related to my confusions
as according to your ASP.NET AJAX Style Folder explorer article i want the add the functionality between gridview and tree view like drag and drop same like yahoo mail beta mean i just drag a file and drop it on specific node of tree view please assist me regarding this functionality
Thnx Regards
Hussnain Raza

Posted by: Michael on January 8, 2008 12:00 AM

Hello Matt: First thank you for sharing. I would like to know if you could help me with a little conversion on your code. What I am trying to do is in the display column add an . When a user clicks on an item in the folder(in my case they are word docs) then contents of the doc are displayed in the not downloaded or have both options! I do work in VB so I an using a converter to convert C to VB.

Posted by: jitendra dwivedi on January 30, 2008 12:00 AM

Very gooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooood

Posted by: Vin on February 27, 2008 12:00 AM

This is a nice feature, but things start to go haywire when you turn on "PopulateOnDemand". Any workaround for this yet?

Posted by: Jacky on April 17, 2008 12:00 AM

good job,can u please send me the source code,thanks for u. My Email:Jacky376@vip.qq.com

Posted by: kumar on May 26, 2008 12:00 AM

hi sir nice work.......
can u pls tell me how to convert this AJAX Style Folder Explorer into CUSTOM Control

wating for ur reply...........

thanks

Posted by: Jim on May 28, 2008 12:00 AM

This one is a custom control

http://www.DigitalTools.com/GVT.aspx

They also have an example of a folder explorer, but theirs is all together so you can sort by Extention, Size or Date - even sort between folders.

Matt, I tried to develop something based on your code and ran into "Enter username and password"-issues - on Firefox only, while it ran perfectly on IE.
Finally this post from Pete Orologas helped to fix it: http://blogs.neudesic.com/blogs/pete_orologas/archive/2006/08/14/224.aspx
(just in case anybody else is struggling with it)

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Michael wrote: Matt, I tried to develop something based on your code and ran into "Enter username and password"-iss...
  • Jim wrote: This one is a custom control http://w...
  • kumar wrote: hi sir nice work....... can u pls tell me how to convert this AJAX Style Folder Explorer into CUSTO...
  • Jacky wrote: good job,can u please send me the source code,thanks for u. My Email:Jacky376@vip.qq.com ...
  • Vin wrote: This is a nice feature, but things start to go haywire when you turn on "PopulateOnDemand". Any wor...
  • jitendra dwivedi wrote: Very gooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooood ...
  • Hussnain Raza wrote: Hi Matt your articles are really very help ful for me here i need some assistancy related to my con...
  • Michael wrote: Hello Matt: First thank you for sharing. I would like to know if you could help me with a little con...