Jika yang dimaksud pertanyaan pada milist tsb adalah pemenuan untuk ASP.NET, bisa bertumpu pada control <asp:Menu> dengan attribut DataSourceID di-set dengan ID dari control <asp:SiteMapDataSource>. Kemudian, kita harus men-set attribut SiteMapProvider dari control SiteMapDataSource tsb dengan sebuah provider yang memungkinkan untuk meng-query element-element menu dari sebuah table di database. As far as I know, provider seperti itu belum ada di ASP.NET, so we need to create it.
Ok, lets get started. In this case I use SQL Server database.
1. Create a table and name it with whatever, such as: Sitemap
Please note the menu hierarchy. For example: menu with title Live Video is the child of Video menu because its PARENT is set to Video menu SITEMAP_ID
2. Create provider class, name it SqlSiteMapProvider.cs, and place it inside App_Code folder. The code should be self-explained. I'll attach the code in this posting
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Runtime.CompilerServices;
using System.Security.Permissions;
using System.Web;
using System.Web.Caching;
using System.Web.Configuration;
/// <summary>
/// SqlSiteMapProvider
/// This class is a site map provider implementation for site map data stored in SQL Server Database.
/// This provider provides caching feature so that it will return root SiteMapNode if available in cache.
/// </summary>
namespace DyCode.Provider
{ [SqlClientPermission(SecurityAction.Demand, Unrestricted = true)]
public class SqlSiteMapProvider : StaticSiteMapProvider
{ private const string ErrMsg1 = "Missing node ID";
private const string ErrMsg2 = "Duplicate node ID";
private const string ErrMsg3 = "Missing parent ID";
private const string ErrMsg4 = "Invalid parent ID";
private const string ErrMsg5 = "Empty or missing connectionStringName";
private const string ErrMsg6 = "Missing connection string";
private const string ErrMsg7 = "Empty connection string";
public const string CacheDependencyName = "__SiteMapCacheDependency";
private string m_ConnectionString; // Database connection string
private string m_TableName = "sys_sitemap"; // TableName that store site map
private string m_CacheKey = "SQLSiteMapProvider_Nodes";
private bool m_CacheEnabled = false;
private int m_IndexID, m_IndexTitle, m_IndexUrl, m_IndexDesc, m_IndexRoles, m_IndexParent;
private Dictionary<int, SiteMapNode> m_NodesDictionary = new Dictionary<int, SiteMapNode>(16);
private readonly object m_Lock = new object();
private SiteMapNode m_RootNode;
public override void Initialize(string name, NameValueCollection config)
{ // Verify that config isn't null
if (config == null)
throw new ArgumentNullException("config");
// Assign the provider a default name if it doesn't have one
if (String.IsNullOrEmpty(name))
name = "SqlSiteMapProvider";
// Add a default "description" attribute to config if the
// attribute doesn’t exist or is empty
if (string.IsNullOrEmpty(config["description"]))
{ config.Remove("description"); config.Add("description", "SQL Server site map provider"); }
// Call the base class's Initialize method
base.Initialize(name, config);
// Initialize m_ConnectionString
string connect = config["connectionStringName"];
if (String.IsNullOrEmpty(connect))
throw new ProviderException(ErrMsg5);
config.Remove("connectionStringName");
if (WebConfigurationManager.ConnectionStrings[connect] == null)
throw new ProviderException(ErrMsg6);
m_ConnectionString = WebConfigurationManager.ConnectionStrings[connect].ConnectionString;
if (String.IsNullOrEmpty(m_ConnectionString))
throw new ProviderException(ErrMsg7);
// Initialize Table Name
string tableName = config["tableName"];
if (!String.IsNullOrEmpty(tableName))
{ m_TableName = tableName;
}
config.Remove("tableName");
// Initialize Cache Enabled/Disabled
string cacheEnabled = config["cacheEnabled"];
if (!String.IsNullOrEmpty(cacheEnabled))
{ m_CacheEnabled = Convert.ToBoolean(cacheEnabled);
}
config.Remove("cacheEnabled");
// Initialize Cache Key
string cacheKey = config["cacheKey"];
if (!String.IsNullOrEmpty(cacheKey))
{ m_CacheKey = cacheKey;
}
config.Remove("cacheKey");
// SiteMapProvider processes the securityTrimmingEnabled
// attribute but fails to remove it. Remove it now so we can
// check for unrecognized configuration attributes.
if (config["securityTrimmingEnabled"] != null)
config.Remove("securityTrimmingEnabled");
// Throw an exception if unrecognized attributes remain
if (config.Count > 0)
{ string attr = config.GetKey(0);
if (!String.IsNullOrEmpty(attr))
throw new ProviderException("Unrecognized attribute: " + attr); }
}
[MethodImpl(MethodImplOptions.Synchronized)]
public override SiteMapNode BuildSiteMap()
{ lock (m_Lock)
{ SqlConnection connection = new SqlConnection(m_ConnectionString);
SqlCommand command = null;
if (m_CacheEnabled)
{ SiteMapNode rootNode = (SiteMapNode)HttpRuntime.Cache.Get(m_CacheKey);
if (rootNode != null)
{ //check dependency
int count = 0;
try
{ command = new SqlCommand("SELECT COUNT(*) FROM " + this.m_TableName, connection); command.CommandType = CommandType.Text;
connection.Open();
count = Convert.ToInt32(command.ExecuteScalar());
}
finally
{ connection.Close();
}
int siteMapNodeCount = Convert.ToInt32(HttpRuntime.Cache.Get(CacheDependencyName));
if (count != siteMapNodeCount)
{ //HttpContext.Current.Response.Write("Remove cache dependency<br/>"); //If table records that store sitemap are changed,
//remove the cache item so that OnSiteMapChanged event is triggered
HttpRuntime.Cache.Remove(CacheDependencyName);
}
else
{ //HttpContext.Current.Response.Write("Return SiteMapNode from cache<br/>"); return (SiteMapNode)HttpRuntime.Cache.Get(m_CacheKey);
}
}
}
// Make sure site map is cleared if it exists before continue
if (m_RootNode != null)
{ ClearSiteMap();
}
// Query the database for site map nodes
try
{ command = new SqlCommand("SELECT SITEMAP_ID, TITLE, DESCRIPTION, URL, ROLES, PARENT FROM " + this.m_TableName + " ORDER BY SITEMAP_ID", connection); command.CommandType = CommandType.Text;
connection.Open();
SqlDataReader reader = command.ExecuteReader();
m_IndexID = reader.GetOrdinal("SITEMAP_ID"); m_IndexUrl = reader.GetOrdinal("URL"); m_IndexTitle = reader.GetOrdinal("TITLE"); m_IndexDesc = reader.GetOrdinal("DESCRIPTION"); m_IndexRoles = reader.GetOrdinal("ROLES"); m_IndexParent = reader.GetOrdinal("PARENT");
if (reader.Read())
{ // Create the root SiteMapNode and add it to the site map
m_RootNode = CreateSiteMapNodeFromDataReader(reader);
AddNode(m_RootNode, null);
// Build a tree of SiteMapNodes underneath the root node
int counter = 1;
while (reader.Read())
{ // Create another site map node and add it to the site map
SiteMapNode node = CreateSiteMapNodeFromDataReader(reader);
AddNode(node, GetParentNodeFromDataReader(reader));
counter++;
}
if (m_CacheEnabled && (HttpRuntime.Cache.Get(m_CacheKey) == null))
{ //Set a value for the cache entry that will serve as the
//key for the dependency to be created on
HttpRuntime.Cache[CacheDependencyName] = counter;
//Create the array of cache key item names
string[] keys = new String[1];
keys[0] = CacheDependencyName;
//Create a dependency object referencing the array of cachekeys (keys)
CacheDependency dependency = new CacheDependency(null, keys);
HttpRuntime.Cache.Insert(m_CacheKey, m_RootNode, dependency,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable,
new CacheItemRemovedCallback(OnSiteMapChanged));
}
}
}
finally
{ connection.Close();
}
// Return the root SiteMapNode
return m_RootNode;
}
}
private void ClearSiteMap()
{ Clear();
m_NodesDictionary.Clear();
m_RootNode = null;
}
protected override SiteMapNode GetRootNodeCore()
{ lock (m_Lock)
{ BuildSiteMap();
return m_RootNode;
}
}
// Helper methods
private SiteMapNode CreateSiteMapNodeFromDataReader(DbDataReader reader)
{ // Make sure the node ID is present
if (reader.IsDBNull(m_IndexID))
throw new ProviderException(ErrMsg1);
// Get the node ID from the DataReader
int id = reader.GetInt32(m_IndexID);
// Make sure the node ID is unique
if (m_NodesDictionary.ContainsKey(id))
throw new ProviderException(ErrMsg2 + "; Title: " + reader.GetString(m_IndexTitle));
// Get title, URL, description, and roles from the DataReader
string title = reader.IsDBNull(m_IndexTitle) ? null : reader.GetString(m_IndexTitle).Trim();
string url = reader.IsDBNull(m_IndexUrl) ? null : reader.GetString(m_IndexUrl).Trim();
string description = reader.IsDBNull(m_IndexDesc) ? null : reader.GetString(m_IndexDesc).Trim();
string roles = reader.IsDBNull(m_IndexRoles) ? null : reader.GetString(m_IndexRoles).Trim();
// If roles were specified, turn the list into a string array
string[] rolelist = null;
if (!String.IsNullOrEmpty(roles))
rolelist = roles.Split(new char[] { ',', ';' }, 4000);
// Create a SiteMapNode
SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description, rolelist, null, null, null);
// Record the node in the m_NodesDictionary dictionary
m_NodesDictionary.Add(id, node);
// Return the node
return node;
}
private SiteMapNode GetParentNodeFromDataReader(DbDataReader reader)
{ // Make sure the parent ID is present
if (reader.IsDBNull(m_IndexParent))
throw new ProviderException(ErrMsg3 + "; Title: " + reader.GetString(m_IndexTitle));
// Get the parent ID from the DataReader
int pid = reader.GetInt32(m_IndexParent);
// Make sure the parent ID is valid
if (!m_NodesDictionary.ContainsKey(pid))
throw new ProviderException(ErrMsg4 + "; Title: " + reader.GetString(m_IndexTitle));
// Return the parent SiteMapNode
return m_NodesDictionary[pid];
}
public void OnSiteMapChanged(string key, object item, CacheItemRemovedReason reason)
{ lock (m_Lock)
{ if (key == CacheDependencyName && reason == CacheItemRemovedReason.DependencyChanged)
{ // Clear the site map
ClearSiteMap();
}
}
}
}
}
3. Create some configurations in web.config, like this:
3.1. Make sure you have a connection string to be used to connect to database that contains the Sitemap table.
3.2. Add SiteMap tag inside <system.web> tag
If you notice, tableName attribute above is set with table name that we have created previously.
4. In the page that will display menu, write this code:
Please note that, SiteMapProvider attribute of SiteMapDataSource control is set with the name of provider that we have registered in web.config at step 3.
5. Test it
In order to use other database, you should change all SQL Server-related stuffs (used ADO.NET classes, SQL statements, and created table) to desired database. You can adapt this code to use OR Mapping technology so database independency can be achieved.
That's it. Enjoy. Please refer to attachment to get the complete code.