Posted on 2007-02-14 22:39
webabcd 阅读(11408)
评论(66) 编辑 收藏 网摘 所属分类:
ASP.NET
[索引页]
[源码下载]
温故知新ASP.NET 2.0(C#)(3) - SiteMap(站点地图)
作者:
webabcd
介绍
ASP.NET 2.0 中的站点导航提供程序向应用程序中的页公开导航信息,使您可以独立于页的实际物理布局定义站点的结构。默认站点导航提供程序基于XML,但通过为站点地图编写自定义提供程序,也可以从任意后端公开此信息。
关键
1、创建.sitemap文件,其实就是一个xml文件,包括有着层次结构的
<siteMapNode>元素
2、
<siteMapNode>元素的属性:
Url - 链接地址
Title - 显示的标题
Description - 描述(ToolTip)
resourceKey - 本地化用的(要在<siteMap>节点加上这个属性enableLocalization=true)
securityTrimmingEnabled - 是否让sitemap支持安全特性
roles - 哪些角色可以访问当前节点,多角色用逗号隔开(需要将securityTrimmingEnabled设置为true)
siteMapFile - 引用另一个sitemap文件
注:应用权限的时候,Web.config中的SiteMap节点的Provider也要有相对应的配置(securityTrimmingEnabled="true")
3、可以通过SiteMap和SiteMapNode类访问站点地图数据
4、自定义站点地图提供程序应该写一个继承自StaticSiteMapProvider的类
5、XmlSiteMapProvider要求站点地图节点具有唯一的URL
示例
SiteMap/Web.sitemap(包括一个有siteMapFile属性的节点)
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/SiteMap/Test.aspx#1" title="首页" description="首页描述">
<siteMapNode url="~/SiteMap/Test.aspx#2" title="频道1" description="频道1描述" />
<siteMapNode url="~/SiteMap/Test.aspx#3" title="频道2" description="频道2描述" />
<siteMapNode siteMapFile="WebChild.sitemap">
</siteMapNode>
<siteMapNode url="~/SiteMap/Test.aspx#4" title="频道4" description="频道4描述" />
</siteMapNode>
</siteMap>

SiteMap/WebChild.sitemap(上面.sitemap文件某个节点的siteMapFile属性所指定的文件)
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/SiteMap/Test.aspx#5" title="频道3" description="频道3">
<siteMapNode url="~/SiteMap/Test.aspx#6" title="栏目1" description="栏目1描述" />
<siteMapNode url="~/SiteMap/Test.aspx#7" title="栏目2" description="栏目2描述" />
<siteMapNode url="~/SiteMap/Test.aspx#8" title="栏目3" description="栏目3描述" />
</siteMapNode>
</siteMap>

站点地图测试
SiteMap/Test.aspx

<%
@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Test.aspx.cs"
Inherits="SiteMap_Test" Title="站点地图测试" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<p>
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:TreeView>
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource2" Orientation="Horizontal">
</asp:Menu>

<%
--显示根节点的数据源--%>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" SiteMapProvider="XmlSiteMapProviderTest" />

<%
--不显示根节点的数据源--%>
<asp:SiteMapDataSource ID="SiteMapDataSource2" runat="server" SiteMapProvider="XmlSiteMapProviderTest"
ShowStartingNode="false" />
</p>
<p>
编码方式访问节点信息如下<br />
<asp:Label ID="lbl" runat="server" BackColor="#DDDDDD" />
</p>
</asp:Content>

SiteMap/Test.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class SiteMap_Test : System.Web.UI.Page


{
protected void Page_Load(object sender, EventArgs e)

{
// 获取当前节点的Title
lbl.Text = "当前节点标题:" + SiteMap.CurrentNode.Title + "<br />";

// 取得url为“~/Default.aspx”的SiteMapNode
SiteMapNode smn = SiteMap.Provider.FindSiteMapNode("~/Default.aspx");
lbl.Text += "Default.aspx节点的Url:" + smn.Url;
}
}

站点地图测试(从数据库读数据)
SiteMap/FromDatabase.aspx

<%
@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="FromDatabase.aspx.cs"
Inherits="SiteMap_FromDatabase" Title="站点地图测试(从数据库读数据)" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1">
</asp:TreeView>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" SiteMapProvider="SqlSiteMapProvider" />
</asp:Content>

自定义站点地图提供程序(SqlServer方式)
SqlSiteMapProvider.cs(“sp_GetSiteMap”为读取站点地图数据的存储过程,详见源码)
using System;
using System.Web;
using System.Data.SqlClient;
using System.Collections.Specialized;
using System.Configuration;
using System.Web.Configuration;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.Security.Permissions;
using System.Data.Common;
using System.Data;


/**//// <summary>
/// SqlSiteMapProvider
/// </summary>
public class SqlSiteMapProvider : StaticSiteMapProvider


{
private string _strCon;
private int _indexID, _indexTitle, _indexUrl, _indexDesc, _indexParent;

// 节点
private SiteMapNode _node;
// 节点字典表
private Dictionary<int, SiteMapNode> _nodes = new Dictionary<int, SiteMapNode>();
// for 线程安全
private readonly object _lock = new object();


/**//// <summary>
/// 初始化
/// </summary>
/// <param name="name">name</param>
/// <param name="config">config</param>
public override void Initialize(string name, NameValueCollection config)

{
// 验证是否有config
if (config == null)
throw new ArgumentNullException("config不能是null");

// 没有provider则设置为默认的
if (String.IsNullOrEmpty(name))
name = "SqlSiteMapProvider";

// 没有描述就增加一个描述
if (string.IsNullOrEmpty(config["description"]))

{
config.Remove("description");
config.Add("description", "SqlSiteMapProvider");
}

// 调用基类的初始化方法
base.Initialize(name, config);

// 初始化连接字符串
string conStringName = config["connectionStringName"];

if (String.IsNullOrEmpty(conStringName))
throw new ProviderException("没找到connectionStringName");

config.Remove("connectionStringName");

if (WebConfigurationManager.ConnectionStrings[conStringName] == null)
throw new ProviderException("根据connectionStringName没找到连接字符串");

// 获得连接字符串
_strCon = WebConfigurationManager.ConnectionStrings[conStringName].ConnectionString;

if (String.IsNullOrEmpty(_strCon))
throw new ProviderException("连接字符串是空的");
}


/**//// <summary>
/// 从持久性存储区加载站点地图信息,并在内存中构建它
/// </summary>
/// <returns></returns>
public override SiteMapNode BuildSiteMap()

{
lock (_lock)

{
// 线程安全的实现
if (_node != null)
return _node;

SqlConnection connection = new SqlConnection(_strCon);

try

{
SqlCommand command = new SqlCommand("sp_GetSiteMap", connection);
command.CommandType = CommandType.StoredProcedure;

connection.Open();
SqlDataReader reader = command.ExecuteReader();

// 获得各个字段的索引
_indexID = reader.GetOrdinal("ID");
_indexUrl = reader.GetOrdinal("Url");
_indexTitle = reader.GetOrdinal("Title");
_indexDesc = reader.GetOrdinal("Description");
_indexParent = reader.GetOrdinal("Parent");

if (reader.Read())

{
// 把第一条记录作为根节点添加
_node = CreateSiteMapNodeFromDataReader(reader);
AddNode(_node, null);

// 构造节点树
while (reader.Read())

{
// 在站点地图中增加一个节点
SiteMapNode node = CreateSiteMapNodeFromDataReader(reader);
AddNode(node, GetParentNodeFromDataReader(reader));
}

}

reader.Close();
}
catch (Exception ex)

{
throw new Exception(ex.ToString());
}
finally

{
connection.Close();
}

// 返回SiteMapNode
return _node;
}
}


/**//// <summary>
/// 将检索目前由当前提供程序管理的所有节点的根节点
/// </summary>
/// <returns></returns>
protected override SiteMapNode GetRootNodeCore()

{
lock (_lock)

{
return BuildSiteMap();
}
}


/**//// <summary>
/// 根据DataReader读出来的数据返回SiteMapNode
/// </summary>
/// <param name="reader">DbDataReader</param>
/// <returns></returns>
private SiteMapNode CreateSiteMapNodeFromDataReader(DbDataReader reader)

{
if (reader.IsDBNull(_indexID))
throw new ProviderException("没找到ID");

int id = reader.GetInt32(_indexID);

if (_nodes.ContainsKey(id))
throw new ProviderException("不能有重复ID");

// 根据字段索引获得相应字段的值
string title = reader.IsDBNull(_indexTitle) ? null : reader.GetString(_indexTitle).Trim();
string url = reader.IsDBNull(_indexUrl) ? null : reader.GetString(_indexUrl).Trim();
string description = reader.IsDBNull(_indexDesc) ? null : reader.GetString(_indexDesc).Trim();

// 新建一个SiteMapNode
SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description);

// 把这个SiteMapNode添加进节点字典表里
_nodes.Add(id, node);

// 返回这个SiteMapNode
return node;
}


/**//// <summary>
/// 得到父节点的SiteMapNode
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
private SiteMapNode GetParentNodeFromDataReader(DbDataReader reader)

{
if (reader.IsDBNull(_indexParent))
throw new ProviderException("父节点不能是空");

int pid = reader.GetInt32(_indexParent);

if (!_nodes.ContainsKey(pid))
throw new ProviderException("有重复节点ID");

// 返回父节点的SiteMapNode
return _nodes[pid];
}


}
上面两个测试页面所需的web.config中的配置
<configuration>
<appSettings/>
<connectionStrings>
<add name="SqlConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Database.mdf;Integrated Security=True;User Instance=True"/>
</connectionStrings>
<system.web>
<siteMap enabled="true" defaultProvider="XmlSiteMapProvider">
<providers>
<add name="XmlSiteMapProvider" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="~/Web.sitemap"/>
<add name="XmlSiteMapProviderTest" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" siteMapFile="~/Sitemap/Web.sitemap"/>
<add name="SqlSiteMapProvider" type="SqlSiteMapProvider" connectionStringName="SqlConnectionString" />
</providers>
</siteMap>
</system.web>
</configuration>
OK
[源码下载]
Feedback
@ivw
兄弟不睡觉的吗?好精神!羡慕中……
@张振
:)
想赶快把这个写完,后面还有好多新的东西要学啊
都习惯了12点后才睡了,早上8点起床上班。哎。真觉得累。
兄弟,知不知道读取硬盘序列号和CPU序列号除了wmi还有没有更好的办法啊?
有例子吗?应该可以在asp.net里运行吧?如果可以的话发到我的邮箱
liangivw@126.com
谢谢
.......................,那有没有这方面的资料啊?
@ivw
偶尔发现,这里居然有我没回复的留言
sorry
看了你连接的那篇文章
序列号的是这个
public uint dwNumberOfProcessors;
下载并安装后生成出错:
错误 102 未能加载类型“AttemptingToLogIntoLockedAccountEvent”。 E:\DynamicSiteMap\Web\Web.config
我是将其放在E:\DynamicSiteMap:下
还请指点!! 比较急,多谢!!!
@海鸥
不好意思,文件传错了,晚上回家后我会把正确的源代码传上来的
那就多谢了啊! 还有就是您写的那个:SqlSiteMapProvider.cs
有没有更多注释呢?或是更经典的。 比方说,可以按任意节点取得其
下面的所有子节点,以及动态控制取子节点的深度(个数)。
万分感谢!!
@海鸥
已经改好传上来了
SqlSiteMapProvider.cs是个provider,它的功能是在其基类中定义好的,并不能自己随便加功能的
SqlSiteMapProvider.cs这个看不太懂,可不可以多加点注释,呵呵!在加载第一个节点的时候运行有错误!!就是CreateSiteMapNodeFromDataReader这个方法下报错!
@清风
这个问题,其实关键的部分我都写上注释了,我觉得你最好先看看provider模式
然后再来看SqlSiteMapProvider
另外,兄弟可以先看一下ms官方的那些批批provider是怎么写的,我在源码里都有提供(其中包括XmlSiteMapProvider.cs)
xml的弄明白了,sql的自然也就明白了
那个provider看不懂,太难了,有没有好材料或网址啊??呵呵
真的很感谢啊!!下次有什么不懂在问你了,有机会请你吃饭,可是没机会,哈哈!!
@清风
好的
怎么能说没有机会
只要在地球上,那就有机会
我会记着,你欠我一顿饭的
好啊!只请米饭没菜!!我终于知道Initialize(string name, NameValueCollection config)这个是干什么了看了半天才明白原来就是取config中的字符串连接啊,怎么这么麻烦啊,我以前在vb.net中用的很简单啊就一句话System.Configuration.ConfigurationSettings.AppSettings("ConnectionString")这样就取出来了啊!!!
@清风
:)
不是的
比如说membership的provider
<add name="SqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="SqlConnectionString"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="3"
minRequiredPasswordLength="3"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression="" />
它还包含好多其他信息,Initialize(string name, NameValueCollection config)是要把他们都读出来的
哦,不好意思,我以为就读"connectionStringName"这个东西了!!明白了
大哥,你的这个东西确实挺管用的 ,但是每次都是从数据库里面去加载数据,如果数据量太大的话,那不是要加载很久的时间哦!能不能实现生成一个静态的Web.SiteMap站点地图文件呢?
你说的那个地方,我去看了下,看不懂,你有做出来的吗?给我传一个行不行,我邮箱是 : baiping2@163.com 不胜感激!!!
还是不行啊 !跟我需要的不一样,我要的是读取数据库的直接生成一个Web.Sitemap的文件来覆盖原来的旧的文件,这样的话我就可以不用每次加载的是时候都从数据库里面去读,而不是每次使用的时候都要先从数据库里面去读一次。
不行啊 ,你说的那个,它并没有 生成Web.Sitemap文件啊!还是每次使用的时候都要从数据库去加载,我的意思是要生成一个文件放在那里,当使用的时候就直接读取Web.Sitemap的的数据,而不从数据库加载,当我需要更新的时候在从数据库里面去加载
@后进
他用了SqlCacheDependency,不会每次都读数据库的
按你说的那实际上就是对xml的增删改操作了,这个跟对其它xml文件的操作没有区别
@webabcd
那就如你说的对Web.Sitemap文件进行增删改操作,该怎么做呢?
@后进
就是和普通的xml的增删改操作一样,可以网上搜一下,有很多
请问,使用自定义站点地图提供程序来提供sitemap的数据,怎么样才能支持外部链接的地址? 我翻了很多资料都找不到,是不是站点地图节点的URL不支持外部链接,我试过了,都是提示安全性错误,不过用web.sitemap就不会出错.
前辈,请教你个问题:
SiteMap可以从数据库中读取,那可不可以传参数?
我是用SiteMap和TreeView来定制子菜单,根据主菜单传递的ID生成子菜单?
这有办法实现吗?
@黄小舒
这样的话,可以动态绑定TreeView啊
请问下,sitemap中启用securityTrimmingEnabled,则父节点设置了roles属性,字节点在不设置roles的情况下默认与父节点有相同权限。有没有方法使得字节点不设置roles就没有访问权限?
@SmilingMoving
“有没有方法使得字节点不设置roles就没有访问权限?”
如果不给任何角色权限,那不就是不需要这个节点啦
谢谢lz的回复,我是想实现一个权限树的功能。管理员选择某角色可以访问哪些页面后,程序就去修改sitemap中对应页面的roles值。相当于是sitemap的每个节点都是独立的,子节点不会去共享父节点的设置的roles值,要是没设置角色就不许他人访问。可现在情况是,假设父节点设置了admin和user两个角色,它的子节点只设置了user角色,可现在admin用户也可以访问该子节点。
请问LZ,出现这样的错误:
提供程序集合中不存在为 defaultProvider 指定的提供程序“XmlSiteMapProvider”。
是不是缺少什么引用啊?
@Savage
如果你的web.config里
指定了siteMap的defaultProvider属性
那么必须要有与其对应的provider的相关配置
@问天何必
:)
呵呵
确实,用不到的可以暂先不看,等用到时或者想学的时候能找到学习资源就好
正在烦treeview和sitemappath并用读数据库
好使啊,用泛型保存节点,不用递进整个树,算法很巧,谢谢了
发现一个问题,更新了数据 库,不会反映出来,大大有什么好的办法
第一次可以读取数据,第二修取了数据库的节点内容,就不更新了,我断点跟踪,发现这里
// 单例模式的实现
if (node != null)
{
return node;
}
这里,第一次加载了根节点的信息,已经存在不再调用sqlreader读第二次更新的数据了。 webabcd你看看是不是有这样的情况
@不知所错
不会啊
我用我的demo测试了一下,修改了数据库的数据是可以获取到的
另外纠正我的一个错误,原来写的是“// 单例模式的实现”,可能是当时脑袋进水了,那部分应该是为了线程安全才写的代码,因为可能会有并发问题
也许吧
我用的是sql2000,打不开的你数库,可不可以转成2000的格式放上来参考一下
装了express2005又不会用
@不知所错
没有啊
不过,应该尽快升级到至少2005,比2000强很多啊
Server Error in '/VS2005' Application.
--------------------------------------------------------------------------------
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.
Parser Error Message: An error occurred loading a configuration file: Failed to start monitoring changes to 'C:\Documents and Settings\studio\桌面\VS2005' because access is denied.
Source Error:
[No relevant source lines]
这是什么问题呢?大哥
@迷茫的小菜
没权限加载配置文件
别在桌面上执行,比如放到c:\试试
@webabcd
LZ,还是不行吖..把权限都改成完全控制了还不行..可能是IIS的问题..
另外请教下LZ.你是怎么学的呢?能不能割爱传授下学习心法...个人感觉自己的学习方法不是很好..学过就忘..很难往深层走..望LZ不吝赐教.
谢了..
Email:skinsen@foxmail.com
@迷茫的小菜
嗯。。。也许
学习的话,就是一个一个地做例子,没什么特别的,如果平时不经常用的话,肯定容易忘,只要用到的时候,能快速高质量地重新拿起来就好