Use LINQ to query a sitemap-like data structure. We are using ASP.NET and our website needs a navigation system. Often, using the Web.sitemap in its default form is sufficient, be here we need a more flexible solution that allows us to generate Google XML sitemaps, allow more per-user customization, or create page series.
The solution will be in the code-behind and C# classes. First, the data source format is not important. It can be in a database, an XML file, or just custom object arrays in code. The data source contains a series of nodes, one for each page in the site, and each node can have any number of properties.
These objects can be constructed from any data source. Due to the limited scope of the project and the fact that my application has limitations due to the server's security mechanism, I will store the SitePage object array in a C# file. What follows are the sitemap properties and some sample code.
| SitePage property | Data type | What it is good for |
| Visibility |
VisibilityType enum value |
Where the page link will be presented (in a supplementary index, or the main page). |
| Url | String | Where the page is stored in our project (relative paths). |
| Frequency | Enum | How often the page is modified (for Google). |
| Category | String | What general category the page is in. |
| Title | String | What the page's title is (for displaying in link lists). |
/// <summary>
/// A single object that corresponds to a page.
/// </summary>
public class SitePage
{
/// <summary>
/// The visibility level of the page in the sitemap.
/// Note the condenced syntax.
/// </summary>
public VisibilityType Visibility { get; set; }
/// <summary>
/// The page's title.
/// </summary>
private string Title { get; set; }
// Several properties and fields omitted.
}
We initialize all the nodes directly in a class in the App_Code folder. Another object must contain arrays or Lists of SitePage. VisibilityType is a simple enum containing different values. You can use a special collection initialization syntax that improves readability.
/// <summary>
/// Initialize all the SitePage objects in the array in this constructor.
/// </summary>
public SiteStructure()
{
_site.Pages = new SitePage[]
{
new SitePage
{
Visibility = VisibilityType.Root, // Show on top-level pages.
Url = "Default.aspx", // The home page URL.
Title = "Blank", // The page title (should be something else).
Category = PageCategory.None // No specific category.
},
new SitePage
{
Visibility = VisibilityType.Regular,
Url = "Content/Anagram-Web-Database.aspx",
Title = "An Anagram Database Web Application",
Category = PageCategory.WebApplications
},
new SitePage
{
Visibility = VisibilityType.Archive,
Url = "Content/Find-First-Set-Cpp.aspx",
Title = "Find First Set: Choices in C++ and VC++",
Category = PageCategory.Performance
}
};
}
We must use the standard query syntax in C#. Here we use LINQ to query the data array and use it in the site. Let's look at a simple special-case first. On the Archives page on this site, I want to list all pages in Visibility of VisibilityType.Archive. Here is some example code.
/// <summary>
/// Build up a string containing all the HTML links to archive pages.
/// </summary>
/// <param name="pageUrlIn">Our site root (virtual) URL.</param>
/// <returns>The HTML string generated.</returns>
public string GetArchiveString(string pageUrlIn)
{
StringBuilder builder = new StringBuilder();
// Select archive pages from the sitemap structure in memory.
// Alphabetize them. We use the var keyword for simpler syntax.
var pageList = from page in _site.Pages
where page.Visibility == VisibilityType.Archive
orderby page.Title
select page;
// Build up a StringBuilder containing HTML markup for the output.
foreach (SitePage page in pageList)
{
builder.Append("<a href=\").Append(page.Url).Append("\">").Append(
page.Title).Append("</a>");
}
return builder.ToString();
}
For global, single-instance data like this, use a singleton pattern. The singleton pattern saves memory and enhances performance and will allow for cleaner code. I use a master page for the site. On the master page file, you can simply do a Response.Write of a function call to your custom sitemap object. (Note that it is far better to use HtmlTextWriter.)
For a hierarchal, tree-like sidebar, you can use the groupby operator to group the categories, alphabetize them, and then alphabetize their pages. groupby allows you to generate IGrouping enumerables, which are fairly simple to use in foreach loops.
Everything above could be done in SQL, but it is all on in-memory object arrays here. LINQ doesn't involve any confusing casts or complicated database connections. LINQ queries provide easier sorting and categorization, and are stateless and extremely flexible. Make custom sitemaps and run queries on them using LINQ.