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="true" defaultProvider="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.
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.
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