自定义SiteMapProvider实现动态加载SITEMAP

一、理论知识

继承自SiteMapProvider

要实现站点导航,在ASP.NET 2.0中最方便的就是SiteMap功能了。如果仅仅使用XmlSiteMapProvider则只能从静态的sitemap文件中影射出导航来,无法反映数据库中存储的导航结构。如果要实现根据数据库生成站点导航,就要开发自己的SiteMapProvider。

SiteMapProvider有4个方法要重写,分别是FindSiteMapNode(根据URL获取节点)、GetChildNodes(获取所有子节点)、GetParentNode(获取父节点)、GetRootNodeCore(获取本SiteMapProvider管理范围内的根节点)。对于SiteMap的使用方来说,例如SiteMapDataSource,通过上述4个方法总能在有限步骤内完成SiteMap相关的查询,例如展开若干层SiteMap,以及获取根SiteMapNode到特定SiteMapNode的路径。所以你只要确保自己的SiteMapProvider类正确实现上述4个方法,就可以用于任何SiteMap查询。

SiteMapProvider的灵活性是非常高的,特能构造严格的树,甚至是有向图,例如一个节点只能有一个父节点,但它却可以是多个节点的子节点。这看上去不那么容易理解,但你确实可以这样做,因为SiteMapProvider可以设计为在多个不同的节点输入到GetChildNodes方法时返回的列表都包含同一个特定的节点。如果你觉得你不需要这种灵活性,而需要使用严格的树,并且树是相对静态的(也就是一次构造树就可以用于多次查询),那就应该考虑继承自StaticSiteMapProvider而不是SiteMapProvider。

继承自StaticSiteMapProvider

实现StaticSiteMapProvider的方式与实现SiteMapProvider的方式是不同的。如果你选择了继承自StaticSiteMapProvider,你就相当于确定了导航模型是严格的树。你只需要负责从持久数据中将树结构描述出来,而StaticSiteMapProvider的基础功能会帮你维护树结构在内存中的副本,并由此而提供上述4个要重写的方法中的3个,只剩下GetRootNodeCore需要由你自己重写。

继承自StaticSiteMapProvider的话,除了GetRootNodeCore需要重写以外,还需要重写BuildSiteMap方法,这个正是StaticSiteMapProvider构建内存中树结构的地方。构建操作所需要的方法也由StaticSiteMapProvider提供了,分别是AddNode和RemoveNode方法,另外还有一个Clear方法可以清空内存中的整个树结构。

结合数据库

一般通过数据库构造SiteMap,需要重写StaticSiteMapProvider的Initialize、GetRootNodeCore和BuildSiteMap方法。

Initialize方法继承自ProviderBase,这是一个所有Provider类的基类。在Initialize方法中你能接收到此Provider的名称与配置信息。你可以将ConnectionString写在此Provider在web.config中的配置节,在Initialize时这些配置键值就会传入,你可以在此时将传入的ConnectionString保存到Provider的私有变量中,但不要在Initialize中构造树,因为它仅仅会被调用一次。

GetRootNodeCore用于返回此SiteMapProvider责任范围内的根节点。因为整个SiteMap可以由多个SiteMapProvider提供的SiteMap构成,在跨越SiteMapProvider责任范围边界时,范围内的根节点就关键的标记。通常的做法是,在实现自己的StaticSiteMapProvider时使用一个私有变量保存代表根节点的那个SiteMapNode,而这个SiteMapNode由BuildSiteMap负责构建。

BuildSiteMap是StaticSiteMapProvider派生类的核心,在这里你需要调用Clear方法清空原来的树,然后查询数据库并利用AddNode和RemoveNode方法构建新的树。需要注意的是,此方法必须是线程安全的,因为可能多个客户端访问页面而导致同时向你的StaticSiteMapProvider查询数据,于是多个线程同时调用BuildSiteMap方法。所以重写的BuildSiteMap方法中,通常一开头进行Clear之后就是lock(this),然后再开始构建树。

注意事项

SiteMapProvider和StaticSiteMapProvider的其他方法你喜欢的话也可以去重写,记得要确保线程安全就是了。StaticSiteMapProvider自带的方法都是线程安全的,如果直接调用的话则可依赖其内部的lock而无需自己lock。

如果你的SiteMapProvider要作为XmlSiteMapProvider的下一级SiteMapProvider,则通过在静态sitemap文件中声明<siteMapNode provider="SomeSiteMapProvider" />来配置。如果你的SiteMapProvider中还要引用另外一个SiteMapProvider,那么父SiteMapProvider仅需要在自己提供的SiteMap中包含下一级SiteMap的根节点,这通常可以通过下一级的SiteMapProvider.GetRootNodeCore方法获取,同时设置子SiteMapProvider的ParentProvider属性为父SiteMapProvider,这就行了。

 

 

二、DEMO演示

/// <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>();

    // 用于单例模式
    private readonly object _lock = new object();

   


   /// <summary>
   /// 初始化
   /// </summary>
   /// <param name="name"></param>
   /// <param name="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("连接字符串是空的");

         Clear();

         sitMapBll.ProductTrcipSitMap();

 


    }

    /// <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()
    {
        //Clear();
       
        lock (_lock)
        {
            return BuildSiteMap();
        }
    }

    /// <summary>
    /// 清除站点地图中的节点。
    /// </summary>
    protected override void Clear()
    {
        lock (_lock)
        {
            _nodes = new Dictionary<int,SiteMapNode>();
            base.Clear();
        }
    }

 

 

    /// <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];
    }


}


 

posted @ 2010-03-31 18:52  dodo-yufan  阅读(3413)  评论(1编辑  收藏  举报