导航、权限管理及 Linq 应用

  最近做一个广告系统,后台管理部分使用 ASP.NET,使用了 VS2008 + .Net 3.5,还是使用我写的 DbEntry 做数据库接口,页面部分大部分使用 ASP.NET Ajax 的 UpdatePanel 来进行更新,效果很不错,而且,速度上也感觉比普通的非 Ajax 页面快。

  而对于权限部分,使用页面级访问控制,读取 Web.Config 的方式,导航使用 html 直接放在母板页中的方式,后来觉得这样,每增加一个页面或对页面改名,就需要修改两个地方,不是一个很好的解决方案。

  以前,在做互联星空的一个项目时,曾经设计了使用 XML 做配置,导入 TreeView 做导航的方式,目前虽然不是使用 TreeView 做导航,应该也一样是适用的。不过,这一次,不想使用 XML,而是考虑直接在程序中使用 List 或 Directory 来表示这个数据结构。

  试着写了一下这个结构,发现,还是使用类似数据库的结构比较简单。于是定义 Category 和 Link 类,另外定义 Navigation 类,增加两个静态字段 Categories 和 Links:
public class Category
{
    private static int IdSeed = 1;

    public int Id { get; set; }
    public string Name { get; set; }

    public Category()
    {
        Id = IdSeed++;
    }
}

public class Link
{
    private static int IdSeed = 1;

    public int Id { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public int Permission { get; set; }
    public bool Hide { get; set; }
    public int Category_Id { get; set; }

    public Link()
    {
        Id = IdSeed++;
    }
}

public static class Navigation
{
    public static readonly List<Category> Categories;
    public static readonly List<Link> Links;
}

  然后,在静态构造函数中初始化数据:
static Navigation()
{
    Categories = new List<Category>()
    {
        new Category(){ Id = 1, Name = "设备管理" },
        new Category(){ Id = 2, Name = "内容管理" },
        new Category(){ Id = 3, Name = "计划" },
        new Category(){ Id = 4, Name = "系统" },
    };

    int FullPermission = (int)(SysUserRole.管理员 | SysUserRole.编辑人员 | SysUserRole.审核人员);

    Links = new List<Link>()
    {
        new Link(){ Url = "Default", Permission = FullPermission, Hide = true },
        // 设备管理
        new Link(){ Name = "新建设备", Url = "PlayerEdit", Permission = FullPermission, Category_Id = 1 },
        new Link(){ Name = "设备列表", Url = "PlayerList", Permission = FullPermission, Category_Id = 1 },
        new Link(){ Url = "PlayerManager", Permission = FullPermission, Hide = true, Category_Id = 1 },
        new Link(){ Category_Id = 1 },
        new Link(){ Name = "新建分组", Url = "PlayerGroupEdit", Permission = FullPermission, Category_Id = 1 },
        new Link(){ Name = "分组列表", Url = "PlayerGroupList", Permission = FullPermission, Category_Id = 1 },
        // 内容管理
        ......
        // 计划
        ......
        // 系统
        new Link(){ Name = "新建帐户", Url = "SysUserEdit", Permission = FullPermission, Category_Id = 4 },
        new Link(){ Name = "帐户列表", Url = "SysUserList", Permission = FullPermission, Category_Id = 4 },
        new Link(){ Category_Id = 4 },
        new Link(){ Name = "新建客户", Url = "CustomerEdit", Permission = FullPermission, Category_Id = 4 },
        new Link(){ Name = "客户列表", Url = "CustomerList", Permission = FullPermission, Category_Id = 4 },
    };
}

  其中,空的 Link 表示显示一个 hr 标签,用来分组,Url 是去掉“.aspx”之后的名字,Permission 用来对页面可访问性进行授权,这种方式,对于 int32 而言,可以提供 32 种权限,应该是足够了,当然,各种权限应该按 2 的幂的方式递增,如 1、2、4、8。这样,在判断是否有权限的时候,只要用“(Permission & p) == p”来判断就可以了。

  导航的 html 改在 Navigation 类中生成,然后填入母板页中的方式,使用 DbEntry 中的 HtmlBuilder 来生成 html 片段:
public static string BuildNavigator(int Permission)
{
    HtmlBuilder hb = HtmlBuilder.New.div.id("accmenu").enter();
    foreach (var c in Categories)
    {
        int count = 0;
        var b = HtmlBuilder.New.tab.div.enter();
        b.tab.tab.div.Class("acctitle").text(c.Name).end.enter();
        b.tab.tab.div.enter();
        var list = Links.Where(p => p.Category_Id == c.Id).ToList();
        foreach (var n in list)
        {
            if (string.IsNullOrEmpty(n.Url))
            {
                b.include("\t\t\t<hr />\r\n");
            }
            else
            {
                if (!n.Hide && (n.Permission & Permission) == Permission)
                {
                    count++;
                    b.include("\t\t\t<img src=\"images/h2.gif\" align=\"absmiddle\" /> ");
                    b.a(n.Url + ".aspx").text(n.Name).end.br.enter();
                }
            }
        }
        b.tab.tab.end.enter().tab.end.enter();
        if (count > 0)
        {
            hb.include(b);
        }
    }
    hb.end.enter();
    return hb.ToString();
}

  上面的代码,会生成类似以下的 html :
<div id="accmenu">
    <div>
        <div class="acctitle">设备管理</div>
        <div>
            <img src="images/h2.gif" align="absmiddle" /> <a href="PlayerEdit.aspx">新建设备</a><br />
            <img src="images/h2.gif" align="absmiddle" /> <a href="PlayerList.aspx">设备列表</a><br />
            <hr />
            <img src="images/h2.gif" align="absmiddle" /> <a href="PlayerGroupEdit.aspx">新建分组</a><br />
            <img src="images/h2.gif" align="absmiddle" /> <a href="PlayerGroupList.aspx">分组列表</a><br />
        </div>
    </div>
    <div>
        ......
    </div>
    <div>
        ......
    </div>
    <div>
        <div class="acctitle">系统</div>
        <div>
            <img src="images/h2.gif" align="absmiddle" /> <a href="SysUserEdit.aspx">新建帐户</a><br />
            <img src="images/h2.gif" align="absmiddle" /> <a href="SysUserList.aspx">帐户列表</a><br />
            <hr />
            <img src="images/h2.gif" align="absmiddle" /> <a href="CustomerEdit.aspx">新建客户</a><br />
            <img src="images/h2.gif" align="absmiddle" /> <a href="CustomerList.aspx">客户列表</a><br />
        </div>
    </div>
</div>

  而,它的显示,使用的是 jQuery 的插件 jQuery Accordion ,按照自己想要风格配置 css,然后在母板页中加入:
jQuery().ready(function(){
    var w = $('#accmenu').accordion({
        header: 'div.acctitle',
        animated: false
    });
    w.activate(CategoryIndex);
});

  之所以要关闭动画效果,是为了根据当前页所在分类,自动激活相应的面板,自动激活的过程如果有动画,显得很奇怪,不过,还没有找到这个 jQuery 插件相关设置初始面板的方法......

  Navigation 类提供取得指定 Url 权限的功能,使用基本的 Linq 语法:
public static int GetPermission(string Url)
{
    var item = Links.Where(p => p.Url == Url).ToList();
    if (item.Count > 0)
    {
        return item[0].Permission;
    }
    return 0;
}

  因为,在生成 html 的时候,考虑了如果按照相应的权限,一个分类下没有任何项目,则不显示这个分类,所以,取 Index 要复杂一些,要根据相应的权限进行分组,所以相应的 Linq 语句也复杂一些,使用了 group by:
public static int GetIndex(string Url, int Permission)
{
    int id = FindCategoryId(Url);
    var item = from p in Links where (p.Permission & Permission) == Permission && p.Category_Id != 0
               group p by p.Category_Id into g select new { Category_Id = g.Key };
    var i = item.ToList().FindIndex(p => p.Category_Id == id);
    return i < 0 ? 0 : i;
}

public static int FindCategoryId(string Url)
{
    var item = Links.Where(p => p.Url == Url).ToList();
    if (item.Count > 0)
    {
        return item[0].Category_Id;
    }
    return 0;
}

  虽然我现在使用的是内存里的数据,不过,因为格式是很标准的数据库格式,所以,要把这个配置项放入数据库表里,或者序列化成 XML,也都是非常方便的  —— 虽然我认为这个必要性不高。

  从实现来看,这个方法的速度应该不会很快,不过,因为数据量小,而且对于页面来说,这些在内存里做的手脚只能算小Case,所以没有明显感觉速度上有任何差异。

  不过,目前对于这个方案,还有一些不满意,比如,Hide 参数考虑改成和 Permission 相似,则可以控制每一项在不同权限下的显示,比单纯的全局 Hide 要灵活得多。再比如,目前没有判断是否会出现两条分割线等等。

  另外一种实现方案是,把数据的定义放在每一个页面里,这样的话,虽然设置分散到了每一个页面,但是却更符合实际情况,而且,页面 Url 也可以通过反射得到,删除页面或者页面改名都更简单,也许是更好的解决方案吧。

  其实,我感觉很好的是,这个程序的页面风格、css 以及一些图片什么的,都是我做的,而且我感觉还挺漂亮的  ,来个运行截图:

你可能感兴趣的:(jquery,应用服务器,配置管理,项目管理,LINQ)