Creating Dynamic ASP.NET SiteMap using LINQ


Creating Dynamic ASP.NET SiteMap using LINQ
 
One of the main files I could not program without, is the web.SiteMap file. The web.SiteMap file is a convenient way to centrally store navigation information and other properties for any ASP.NET web site. The web.SiteMap works perfectly with controls such as the Menu or Treeview control. One little known class you can use to create your own SiteMap Provider is the StaticSiteMapProviderclass. This is a base class for the XmlSiteMapProvider class, so by inheriting from this class, you can implement your own SiteMap Provider model. This article will generate a custom SiteMap Provider to create a menu structure outlining the files and folders in the website.
Open Visual Studio 2008 and choose File > New > Web > ASP.NET Web Application.
 
Add a new class to the website and name it MyCustomSiteMap. Add the following code to the class:
 
C#
public class MyCustomSiteMap : StaticSiteMapProvider
    {
        private SiteMapNode parentNode
        {
            get;
            set;
        }
 
        private string ExcludedFolders
        {
            get
            {
                return "(App_Data)|(obj)";
            }
        }
 
        private string ExcludedFiles
        {
            get
            {
                return "";
            }
        }
 
        public override SiteMapNode BuildSiteMap()
        {
            lock (this)
            {
                parentNode = HttpContext.Current.Cache["SiteMap"as SiteMapNode;
                if (parentNode == null)
                {                   
                    base.Clear();                   
                    parentNode = new SiteMapNode(this,
                                            HttpRuntime.AppDomainAppVirtualPath,
                                            HttpRuntime.AppDomainAppVirtualPath +"Default.aspx",
                                            "Home");
                   
                    AddNode(parentNode);
                    AddFiles(parentNode);
                    AddFolders(parentNode);
                   
                    HttpContext.Current.Cache.Insert("SiteMap", parentNode);
                }
                return parentNode;
            }
        }
 
        private void AddFolders(SiteMapNode parentNode)
        {
            var folders = from o inDirectory.GetDirectories(HttpContext.Current.Server.MapPath(parentNode.Key))
                          let dir = new DirectoryInfo(o)
                          where !Regex.Match(dir.Name, ExcludedFolders).Success
                          select new
                          {
                              DirectoryName = dir.Name
                          };
           
            foreach (var item in folders)
            {
                string folderUrl = parentNode.Key + item.DirectoryName;
                SiteMapNode folderNode = new SiteMapNode(this,
                                    folderUrl,
                                    null,
                                    item.DirectoryName,
                                    item.DirectoryName);
               
                AddNode(folderNode, parentNode);
                AddFiles(folderNode);
            }
        }
 
        private void AddFiles(SiteMapNode folderNode)
        {
            var files = from o inDirectory.GetFiles(HttpContext.Current.Server.MapPath(folderNode.Key))
                        let fileName = new FileInfo(o)                       
                        select new
                        {
                            FileName = fileName.Name                            
                        };
           
            foreach (var item in files)
            {
                SiteMapNode fileNode = new SiteMapNode(this,
                                    item.FileName,
                                    folderNode.Key + "/" + item.FileName,
                                    item.FileName);
                AddNode(fileNode, folderNode);
            }
        }
 
        protected override SiteMapNode GetRootNodeCore()
        {
            return BuildSiteMap();
        }
    }
 
VB.NET
Public Class MyCustomSiteMap
      Inherits StaticSiteMapProvider
            Private privateparentNode As SiteMapNode
            Private Property parentNode() As SiteMapNode
                  Get
                        Return privateparentNode
                  End Get
                  Set(ByVal value As SiteMapNode)
                        privateparentNode = value
                  End Set
            End Property
 
            Private ReadOnly Property ExcludedFolders() As String
                  Get
                        Return "(App_Data)|(obj)"
                  End Get
            End Property
 
            Private ReadOnly Property ExcludedFiles() As String
                  Get
                        Return ""
                  End Get
            End Property
 
            Public Overrides Function BuildSiteMap() As SiteMapNode
                  SyncLock Me
                        parentNode = TryCast(HttpContext.Current.Cache("SiteMap"), SiteMapNode)
                        If parentNode Is Nothing Then
                              MyBase.Clear()
                              parentNode = New SiteMapNode(Me, HttpRuntime.AppDomainAppVirtualPath, HttpRuntime.AppDomainAppVirtualPath & "Default.aspx", "Home")
 
                              AddNode(parentNode)
                              AddFiles(parentNode)
                              AddFolders(parentNode)
 
                              HttpContext.Current.Cache.Insert("SiteMap", parentNode)
                        End If
                        Return parentNode
                  End SyncLock
            End Function
 
            Private Sub AddFolders(ByVal parentNode As SiteMapNode)
                  Dim folders = _
                        From o InDirectory.GetDirectories(HttpContext.Current.Server.MapPath(parentNode.Key)) _
                        Let dir = New DirectoryInfo(o) _
                        Where (Not Regex.Match(dir.Name, ExcludedFolders).Success) _
                        Select New With {Key .DirectoryName = dir.Name}
 
                  For Each item In folders
                        Dim folderUrl As String = parentNode.Key + item.DirectoryName
                        Dim folderNode As New SiteMapNode(Me, folderUrl, Nothing, item.DirectoryName, item.DirectoryName)
 
                        AddNode(folderNode, parentNode)
                        AddFiles(folderNode)
                  Next item
            End Sub
Private Sub AddFolders(ByVal parentNode As SiteMapNode)
                  Dim folders = _
                        From o InDirectory.GetDirectories(HttpContext.Current.Server.MapPath(parentNode.Key)) _
                        Let dir = New DirectoryInfo(o) _
                        Where (Not Regex.Match(dir.Name, ExcludedFolders).Success) _
                        Select New With {Key .DirectoryName = dir.Name}
 
                  For Each item In folders
                        Dim folderUrl As String = parentNode.Key + item.DirectoryName
                        Dim folderNode As New SiteMapNode(Me, folderUrl, Nothing, item.DirectoryName, item.DirectoryName)
 
                        AddNode(folderNode, parentNode)
                        AddFiles(folderNode)
                  Next item
End Sub
 
            Private Sub AddFiles(ByVal folderNode As SiteMapNode)
                  Dim files = _
                        From o InDirectory.GetFiles(HttpContext.Current.Server.MapPath(folderNode.Key)) _
                        Let fileName = New FileInfo(o) _
                        Select New With {Key .FileName = fileName.Name}
 
                  For Each item In files
                        Dim fileNode As New SiteMapNode(Me, item.FileName, folderNode.Key & "/" & item.FileName, item.FileName)
                        AddNode(fileNode, folderNode)
                  Next item
            End Sub
 
            Protected Overrides Function GetRootNodeCore() As SiteMapNode
                  Return BuildSiteMap()
            End Function
 
End Class
 
The class inherits the StaticSiteMapProvider class. The two methods we must implement are GetRootNodeCore and BuildSiteMap. BuildSiteMap is where we most of the work lives, so let’s look at that code a now.
To ensure only one thread create an instance of this class, we use the lock keyword. Once we have a lock, we proceed to check the Cache to see if the SiteMap has already been created. If the Cache is empty, we use LINQ to enumerate through the folders and files of the website. To exclude folders you may not want to appear in the list, a property named ExcludedFolders returns a string which is used in a regular expression in the AddFolders method. You could create a separate property to exclude particular files, but for this example we’ll stick to just folders.
The AddFiles method utilises LINQ to enumerate through the files in each folder that is not in the ExcludedFolders string. After a file is found, a new SiteMapNode is created and added to the parent SiteMapNode object, which will be the folder the file is in.
To use this in your website, you need to add the following code to the web.config file:
<siteMap enabled="truedefaultProvider="MyCustomSiteMap">
                  <providers>
                        <clear/>
                        <add name="MyCustomSiteMap"type="CustomSiteMap.MyCustomSiteMap"/>
                  </providers>
            </siteMap>
 
The code above informs ASP.NET to use our CustomSiteMap.MyCustomSiteMap type as the default SiteMap provider. 
 
The hard work is done. To view the results add a new web form to the solution.
 
DS LINQ 
 
In the new web form, drag and drop a Menu and SiteMapDataSource control to the page.
 
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:Menu>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
 
By using the SiteMapDataSource control with the Menu, ASP.NET will now use the custom SiteMap class that we created earlier.
Add a new folder to the project and name it Data. Next add some text files to the folder. This is purely to display some more data for this example. Everything is complete. If you run the project, the menu will display the folders and files in the website.
DS LINQ
The example above will hopefully get you thinking of ways to automatically make things happen when you’re developing a website. Instead of having to manually add a web.SiteMap file and update it each time a new page or folder is added, why not create a process to add it automatically? This will allow you to spend more time doing the things you want to, namely writing code! The entire source code of this article can be downloaded from here