本文描述如何创建 ASP.NET 网站地图提供者并配置 ASP.NET 应用程序对相应的网站地图提供者进行使用。
下表列出相关主题的内容,并包括了两个用 C# 编写的网站地图提供者实例。实例一定义的提供者使用 .NET Framework ODBC 数据提供者连接到 ODBC 数据源。另一实例则使用 Microsoft Access 数据库作为数据源。
展示一个完整的基于纯文本的网站地图提供者。
下例代码演示如何创建实现 SiteMapProvider
抽象类的自定义类。
using System; using System.Configuration.Provider; using System.Collections; using System.Collections.Specialized; using System.IO; using System.Security.Permissions; using System.Web; namespace Samples.AspNet.CS { [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)] public class SimpleTextSiteMapProvider : SiteMapProvider { private SiteMapProvider parentSiteMapProvider = null; private string simpleTextProviderName = null; private string sourceFilename = null; private SiteMapNode rootNode = null; private ArrayList siteMapNodes = null; private ArrayList childParentRelationship = null; // 默认构造器。Name 属性在 Initialize 方法中初始化。 public SimpleTextSiteMapProvider() { } // 实现 CurrentNode 属性。 public override SiteMapNode CurrentNode { get { string currentUrl = FindCurrentUrl(); // 找出呈现当前页面的 SiteMapNode。 SiteMapNode currentNode = FindSiteMapNode(currentUrl); return currentNode; } } // 实现 RootNode 属性。 public override SiteMapNode RootNode { get { return rootNode; } } // 实现 ParentProvider 属性。 public override SiteMapProvider ParentProvider { get { return parentSiteMapProvider; } set { parentSiteMapProvider = value; } } // 实现 RootProvider 属性。 public override SiteMapProvider RootProvider { get { // 如果当前实例属于提供者结构中的一员,它就不可以作为 RootProvider。依赖于 ParentProvider。 if (this.ParentProvider != null) { return ParentProvider.RootProvider; } // 如果当前实例没有 ParentProvider,那就不是结构中的一员,并且可以作为 RootProvider。 else { return this; } } } // 实现 FindSiteMapNode 方法。 public override SiteMapNode FindSiteMapNode(string rawUrl) { // 根节点与 URL 匹配吗? if (RootNode.Url == rawUrl) { return RootNode; } else { SiteMapNode candidate = null; // 获取与 URL 匹配的 SiteMapNode。 lock (this) { candidate = GetNode(siteMapNodes, rawUrl); } return candidate; } } // 实现 GetChildNodes 方法。 public override SiteMapNodeCollection GetChildNodes(SiteMapNode node) { SiteMapNodeCollection children = new SiteMapNodeCollection(); // 遍历 ArrayList 并找出所有把指定节点当成父节点的节点集。 lock (this) { for (int i = 0; i < childParentRelationship.Count; i++) { string nodeUrl = ((DictionaryEntry)childParentRelationship[i]).Key as string; SiteMapNode parent = GetNode(childParentRelationship, nodeUrl); if (parent != null && node.Url == parent.Url) { // 包含 Url 的 SiteMapNode 与指定节点的子节点 nodeUrl 进行通信,并为 nodeUrl 获取相应的 SiteMapNode。 SiteMapNode child = FindSiteMapNode(nodeUrl); if (child != null) { children.Add(child as SiteMapNode); } else { throw new Exception("ArrayLists not in sync."); } } } } return children; } protected override SiteMapNode GetRootNodeCore() { return RootNode; } // 实现 GetParentNode 方法。 public override SiteMapNode GetParentNode(SiteMapNode node) { // 检查 childParentRelationship 表并找出当前节点的父节点。 // 如果父节点不存在,RootNode 就是当前节点。 SiteMapNode parent = null; lock (this) { // 获取 childParentRelationship 中的节点的 Value。 parent = GetNode(childParentRelationship, node.Url); } return parent; } // 实现 ProviderBase.Initialize 属性。 // 初始化用于初始 Provider 所持有的状态,但并不建立实际的网站地图。 public override void Initialize(string name, NameValueCollection attributes) { lock (this) { base.Initialize(name, attributes); simpleTextProviderName = name; sourceFilename = attributes["siteMapFile"]; siteMapNodes = new ArrayList(); childParentRelationship = new ArrayList(); // 在内存中建立网站地图。 LoadSiteMapFromStore(); } } // 私有的辅助方法 private SiteMapNode GetNode(ArrayList list, string url) { for (int i = 0; i < list.Count; i++) { DictionaryEntry item = (DictionaryEntry)list[i]; if ((string)item.Key == url) return item.Value as SiteMapNode; } return null; } // 获取当前被显示页面的 URL。 private string FindCurrentUrl() { try { // 当前 HttpContext。 HttpContext currentContext = HttpContext.Current; if (currentContext != null) { return currentContext.Request.RawUrl; } else { throw new Exception("HttpContext.Current is Invalid"); } } catch (Exception e) { throw new NotSupportedException("This provider requires a valid context.",e); } } protected virtual void LoadSiteMapFromStore() { string pathToOpen; lock (this) { // 如果存在根节点,说明 LoadSiteMapFromStore 早已经被调用,可以返回。 if (rootNode != null) { return; } else { pathToOpen = HttpContext.Current.Server.MapPath("~" + "\\" + sourceFilename); if (File.Exists(pathToOpen)) { // 打开文件进行读入。 using (StreamReader sr = File.OpenText(pathToOpen)) { // 清除集合和 rootNode 的状态 rootNode = null; siteMapNodes.Clear(); childParentRelationship.Clear(); // 解析文件并建立网站地图 string s = ""; string[] nodeValues = null; SiteMapNode temp = null; while ((s = sr.ReadLine()) != null) { // 建立若干 SiteMapNode 对象并添加到 ArrayList 集合中。 // 格式:URL,TITLE,DESCRIPTION,PARENTURL nodeValues = s.Split(','); temp = new SiteMapNode(this, HttpRuntime.AppDomainAppVirtualPath + "/" + nodeValues[0], HttpRuntime.AppDomainAppVirtualPath + "/" + nodeValues[0], nodeValues[1], nodeValues[2]); // 这是根节点吗? if (null == rootNode && (null == nodeValues[3] || nodeValues[3] == String.Empty)) { rootNode = temp; } // 如果不是根节点,则将其添加到不同的集合中。 else { siteMapNodes.Add(new DictionaryEntry(temp.Url, temp)); // 父节点已经添加在集合中。 SiteMapNode parentNode = FindSiteMapNode(HttpRuntime.AppDomainAppVirtualPath + "/" + nodeValues[3]); if (parentNode != null) { childParentRelationship.Add(new DictionaryEntry(temp.Url, parentNode)); } else { throw new Exception("Parent node not found for current node."); } } } } } else { throw new Exception("File not found"); } } } return; } } }
该代码实例使用名为 SiteMap.txt 的文本文件,文件内容由逗号进行分隔,并按照指定结构保存网站地图的信息。文件第一行展示了网站地图的根目录,余下部分则代表子节点。并且每个子节点都通过 URL 对父节点进行识别。
default.aspx,Home,MyCompany Home Page, sale.aspx,Now On Sale,Check Out These Great Deals!,default.aspx catalog.aspx,Online Catalog,Browse Our Many Great Items!,default.aspx
SimpleTextSiteMapProvider
实现了 SiteMapProvider
类的所有属性和方法。
最后,SimpleTextSiteMapProvider
在 Web.config 文件中被配置为默认提供者,如下所示。
<configuration> <system.web> <siteMap defaultProvider="SimpleTextSiteMapProvider"> <providers> <add name="SimpleTextSiteMapProvider" type="<type name>" siteMapFile = "<path>/siteMap.txt" /> </providers> </siteMap> </system.web> </configuration>
要自定义该实例,请在自定义网站地图提供者的现实中把 <type name>
替换成完整名称。比如,在上例 C# 代码中,则需要把 <type name>
替换成 Samples.AspNet.CS.SimpleTextSiteMapProvider
。如果对网站地图数据提供者进行编译并将其放在 Bin 目录中,<type name>
串中还必须包含除去扩展名以外的已编译文件名称。比如,如果把上例 C# 代码编译成 Sample.AspNet.dll 文件,<type name>
则应该替换成 Samples.AspNet.CS.SimpleTextSiteMapProvider.Samples.AspNet
。最后,把 <path>
替换成网站地图文件的相关路径。
提示:当被继承类是 SiteMapProvider
而不是 StaticSiteMapProvider
时,必须注意对这些成员的重载:GetRootNodeCore
,FindSiteMapNode
,GetChildNodes
,和 GetParentnode
。
展示一个完整的基于 Access 数据库的网站地图提供者。
下例代码演示如何扩展 StaticSiteMapProvider
类并使用 Microsoft Access 作为网站地图提供者。该提供者使用 .NET Framework OLE DB 数据提供者连接到 Access 数据库。
namespace Samples.AspNet.CS.Controls { using System; using System.Collections; using System.Collections.Specialized; using System.Data; using System.Data.OleDb; using System.Security.Permissions; using System.Web; /// 这是一个非常简单的 AccessSiteMapProvider 类,仅支持含有 1 层深度节点层次的网站地图。 [AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Minimal)] public class AccessSiteMapProvider : StaticSiteMapProvider { private SiteMapNode rootNode = null; private OleDbConnection accessConnection = null; // 该字符串对大小写敏感。 private string AccessConnectionStringName = "accessSiteMapConnectionString"; // 实现默认构造器。 public AccessSiteMapProvider () { } // 一些用于跟踪提供者初始化状态的状态信息。 private bool initialized = false; public virtual bool IsInitialized { get { return initialized; } } // 返回当前网站地图的根节点。 public override SiteMapNode RootNode { get { SiteMapNode temp = null; temp = BuildSiteMap(); return temp; } } protected override SiteMapNode GetRootNodeCore() { return RootNode; } // 初始化处理用来初始化属性值以及 AccessProvider 保持的任何状态信息,但是不用于建立网站地图。 // 网站地图在 BuildSiteMap 方法时才被创建。 public override void Initialize(string name, NameValueCollection attributes) { if (IsInitialized) return; base.Initialize(name, attributes); // 创建并测试 Microsoft Access 数据库的连接。 // 从哈希属性集中获取 Access 连接字符串。 string connectionString = attributes[AccessConnectionStringName]; if (null == connectionString || connectionString.Length == 0) throw new Exception ("The connection string was not found."); else accessConnection = new OleDbConnection(connectionString); initialized = true; } /// /// 被继承的 SiteMapProvider 和 StaticSiteMapProvider 方法必须进行重载。 /// // 清除任何保持过的所有集合或其他状态。 protected override void Clear() { lock (this) { rootNode = null; base.Clear(); } } // 建立来自于持续存储设备的内存呈现,并返回网站地图的根节点。 public override SiteMapNode BuildSiteMap() { // 一旦 SiteMap 类静态化,确保其在网站地图被建立之前不会被更改。 lock(this) { // 如果没有初始化操作,该方法会引发异常。 if (! IsInitialized) { throw new Exception("BuildSiteMap called incorrectly."); } // 如果没有根节点,那么网站地图也就不存在。 if (null == rootNode) { // 先从清理状态开始 Clear(); // 选择来自于 Microsoft Access 的网站地图根节点。 int rootNodeId = -1; if (accessConnection.State == ConnectionState.Closed) accessConnection.Open(); OleDbCommand rootNodeCommand = new OleDbCommand("SELECT nodeid, url, name FROM SiteMap WHERE parentnodeid IS NULL", accessConnection); OleDbDataReader rootNodeReader = rootNodeCommand.ExecuteReader(); if(rootNodeReader.HasRows) { rootNodeReader.Read(); rootNodeId = rootNodeReader.GetInt32(0); // 创建引用当前 StaticSiteMapProvider 的 SiteMapNode。 rootNode = new SiteMapNode(this, rootNodeId.ToString(), rootNodeReader.GetString(1), rootNodeReader.GetString(2)); } else return null; rootNodeReader.Close(); // 选择根节点的子节点集。 OleDbCommand childNodesCommand = new OleDbCommand("SELECT nodeid, url, name FROM SiteMap WHERE parentnodeid = ?", accessConnection); OleDbParameter rootParam = new OleDbParameter("parentid", OleDbType.Integer); rootParam.Value = rootNodeId; childNodesCommand.Parameters.Add(rootParam); OleDbDataReader childNodesReader = childNodesCommand.ExecuteReader(); if (childNodesReader.HasRows) { SiteMapNode childNode = null; while(childNodesReader.Read()) { childNode = new SiteMapNode(this, childNodesReader.GetInt32(0).ToString(), childNodesReader.GetString(1), childNodesReader.GetString(2)); // 使用 SiteMapNode 的 AddNode 方法为 SiteMapNode 添加 ChildNodes 集合。 AddNode(childNode, rootNode); } } childNodesReader.Close(); accessConnection.Close(); } return rootNode; } } } }
为上例代码创建用于网站地图数据存储的 Access 数据表,并在 Access 数据库中提交下例数据定义查询,然后保存为 Sitemap.mdb。
CREATE TABLE SiteMap ( URL Text (255) UNIQUE, NAME Text (255) NOT NULL, PARENTNODEID Int32, CONSTRAINT NODEID PRIMARY KEY (URL, NAME) )
下列表展示了数据表的数据,被列出的文件可以是 Web 站点中已经存在的也可以是需要并且暂时未被创建的。下表显示一个文件列表的示例。
NODEID | URL | NAME | PARENTNODEID |
---|---|---|---|
1 |
Default.aspx |
Default |
null (empty cell) |
2 |
Catalog.aspx |
Catalog |
1 |
3 |
Aboutus.aspx |
Contact Us |
1 |
提示:因为数据源包含不同的 SQL 语法,而部分命令只能与一个数据源一起工作,不能够与其他数据源一起工作。作为执行结果,我们推荐创建数据源专用的网站地图提供者,即使访问数据源时使用的是用于 ODBC 或者 OLE DB 的 .NET Framework 数据提供者(比如,SybaseSiteMapProvider
, OracleSiteMapProvider
,等等)。
StaticSiteMapProvider
类的继承类 AccessSiteMapProvider
会使用基本的 SQL 查询以及 OleDbCommand
和 OleDbDataReader
对象从 Access 数据库中获取数据。
最后,在 Web.config 文件中把 AccessSiteMapProvider
配置成默认提供者,如下所示。
<configuration> <system.web> <siteMap defaultProvider="AccessSiteMapProvider"> <providers> <add name="AccessSiteMapProvider" type="<type name>" accessSiteMapConnectionString="PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=<path>\sitemap.mdb "/> </providers> </siteMap> </system.web> </configuration>
要对该实例进行自定义,请把 <type name>
替换成自定义网站地图数据提供者的完整名称。比如,在上例 C# 代码中,就需要把 <type name>
替换成 Samples.AspNet.CS.Controls.AccessSiteMapProvider
。如果要把编译后的自定义网站地图提供者存放到 Bin 目录,那么 <type name>
串中还必须包括有除去扩展名部分的已编译文件名称。比如,如果把上例 C# 代码编译成 Samples.AspNet.dll 文件,那么 <type name>
将被替换成 Samples.AspNet.CS.Controls.AccessSiteMapProvider.Samples.AspNet
。最后,把 <path>
替换成网站地图数据库的绝对物理路径。
提示:当从 StaticSiteMapProvider
派生自定义类时,必须重载这些成员:BuildSiteMap
和 GetRootNodeCore
。
在对 StaticSiteMapProvider
类进行扩充时应该注意三个最重要的方法:GetRootNodeCore
, Initialize
,和 BuildSiteMap
。而 Clear
和 FindSiteMapNode
方法的默认实现对于大部分自定义提供者都是够用的。
把源代码存放到应用程序的 App_Code 目录。
提示:如果应用程序的 App_Code 目录已经存放有其他源代码,就必须添加与目录中现有代码使用同一种编程语言编写的网站地图提供者版本。
ASP.NET 在应用程序被请求时对提供者进行编译。
--或者--
另外,也可以把网站地图文件编译成库并存放在 Web 应用程序的 Bin 目录,或者使用强命名后存放到全局汇编缓存(GAC)中。比如,下例命令行说明该如何使用命令行编译器来编译实例中的网站地图提供者。
csc /out:<example_name>.dll /t:library <example_name>.cs /r:System.Web.dll /r:System.Configuration.dll
配置应用程序使用网站地图提供者,并在 Web.config 文件中添加提供者的引用。
添加使用网站地图提供者的控件。一旦 Web.config 文件被改变且提供者也被编译之后,提供者会把导航数据装载到内存中的 SiteMap
实例。这时候导航数据就可以被用于任何网站地图架构组件(如 SiteMapPath
,TreeView
,和 Menu
控件),为用户显示网站地图信息。下例代码在 ASP.NET 页面中就同时使用了这三种控件。
<%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Simple Navigation Controls</title> </head> <body> <form id="form1" runat="server"> <div> <h2>Using SiteMapPath</h2> <asp:SiteMapPath ID="SiteMapPath1" Runat="server"> </asp:SiteMapPath> <asp:SiteMapDataSource ID="SiteMapDataSource1" Runat="server" /> <h2>Using TreeView</h2> <asp:TreeView ID="TreeView1" Runat="Server" DataSourceID="SiteMapDataSource1"> </asp:TreeView> <h2>Using Menu</h2> <asp:Menu ID="Menu2" Runat="server" DataSourceID="SiteMapDataSource1"> </asp:Menu> <h2>Using a Horizontal Menu</h2> <asp:Menu ID="Menu1" Runat="server" DataSourceID="SiteMapDataSource1" Orientation="Horizontal" StaticDisplayLevels="2" > </asp:Menu> </div> </form> </body> </html>