`
梁利锋
  • 浏览: 80731 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

导航、权限管理及 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 以及一些图片什么的,都是我做的,而且我感觉还挺漂亮的  ,来个运行截图:
  • 描述: 运行截图
  • 大小: 175.9 KB
分享到:
评论
7 楼 梁利锋 2008-05-19  
PostSharp 很酷,而且效率比 Reflection 高,值得一试。
6 楼 梁利锋 2008-05-16  
哦,原来这样。
DynamicProxy 确实不错,通用性很好。
5 楼 oldrev 2008-05-16  
因为对于一些不考虑客户端的web程序来说,似乎没必要自己调用自己的 WCF web 服务,不过现在我用 DynamicProxy 做了一个等价的拦截器解决了这个问题。
4 楼 梁利锋 2008-05-15  
@oldrev
为什么感觉效率很低呢?

就我自己的开发经验,感觉效率低,和真的效率低是两回事。

大部分时候,业务逻辑本身和数据库访问才是瓶颈,这时,权限系统效率的高低对于用户体验来说,没有实质性影响。
3 楼 oldrev 2008-05-14  
最近做了一个勉强算通用的权限系统,用 Attribute 给每一个 WCF 的 IService 方法分配一个 GUID,然后用拦截消息头检查自定义的 SessionId,总感觉效率很低
2 楼 梁利锋 2008-04-27  
@oldrev
谢谢 
1 楼 oldrev 2008-04-27  
很少见这么出色的 Web 系统界面!

相关推荐

    ASP.NET3.5从入门到精通

    第 4 章 ASP.NET 的网页代码模型及生命周期 4.1 ASP.NET 的网页代码模型 4.1.1 创建ASP.NET 网站 4.1.2 单文件页模型 4.1.3 代码隐藏页模型 4.1.4 创建ASP.NET Web Application 4.1.5 ASP.NET 网站和ASP.NET 应用...

    ASP.NET 3.5 开发大全11-15

    第4章 ASP.NET的网页代码模型及生命周期 4.1 ASP.NET的网页代码模型 4.1.1 创建ASP.NET网站 4.1.2 单文件页模型 4.1.3 代码隐藏页模型 4.1.4 创建ASP.NET Web Application 4.1.5 ASP.NET网站和ASP.NET应用程序的区别...

    ASP.NET 3.5 开发大全

    第4章 ASP.NET的网页代码模型及生命周期 4.1 ASP.NET的网页代码模型 4.1.1 创建ASP.NET网站 4.1.2 单文件页模型 4.1.3 代码隐藏页模型 4.1.4 创建ASP.NET Web Application 4.1.5 ASP.NET网站和ASP.NET应用程序的区别...

    ASP.NET 3.5 开发大全1-5

    第4章 ASP.NET的网页代码模型及生命周期 4.1 ASP.NET的网页代码模型 4.1.1 创建ASP.NET网站 4.1.2 单文件页模型 4.1.3 代码隐藏页模型 4.1.4 创建ASP.NET Web Application 4.1.5 ASP.NET网站和ASP.NET应用程序的区别...

    ASP.NET 3.5 开发大全word课件

    第4章 ASP.NET的网页代码模型及生命周期 4.1 ASP.NET的网页代码模型 4.1.1 创建ASP.NET网站 4.1.2 单文件页模型 4.1.3 代码隐藏页模型 4.1.4 创建ASP.NET Web Application 4.1.5 ASP.NET网站和ASP.NET应用程序的区别...

    ASPNET35开发大全第一章

    第4章 ASP.NET的网页代码模型及生命周期 4.1 ASP.NET的网页代码模型 4.1.1 创建ASP.NET网站 4.1.2 单文件页模型 4.1.3 代码隐藏页模型 4.1.4 创建ASP.NET Web Application 4.1.5 ASP.NET网站和ASP.NET应用程序的区别...

    圣殿祭司的ASP.NET 4.0专家技术手册,完整扫描版

    ASP.NET 4.0技术概述、ASP.NET程序的编译模型、将ASP.NET程序开发服务器Port固定的技巧、C# 4.0语言新功能、对象初始化程序、LINQ架构概述、LINQ标准查询运算符、跨页发送、ASP.NET网页指令、My对象的内涵、C# 直接...

    Visual C#2008开发经验与技巧宝典 源码

    包括LINQ在SQL、Data Set、XML和Object等领域的实际应用,WPF动画界面设计,WCF多层数据架构通信、泛型应用、XML文件处理、多线程管理、程序异常拦截、文件压缩及解压缩、文件加密和解密、文件访问权限、API函数调用...

    ASP.NET4高级程序设计第4版 带目录PDF 分卷压缩包 part1

    因权限只能到60MB,分卷压缩了,共3个压缩包,需下载完3个一起解压, ============================== ASP.NET 4高级程序设计(第4版)》【原版书为:Pro ASP.NET 4 in C# 2010】是ASP.NET领域的鸿篇巨制,全面讲解了...

    ASP.NET4高级程序设计(第4版) 3/3

    2.2.1 解决方案资源管理器 28 2.2.2 文档窗口 29 2.2.3 工具箱 29 2.2.4 错误列表和任务列表 30 2.2.5 服务器资源管理器 31 2.3 代码编辑器 32 2.3.1 添加程序集引用 33 2.3.2 智能感知和大纲显示 ...

    C#程序开发范例宝典(第2版).part13

    实例066 利用选择控件实现权限设置 83 实例067 利用选择控件实现复杂查询 85 2.6 ListView控件应用 87 实例068 ListView控件间的数据移动 87 实例069 将数据库数据添加到ListView控件 90 实例070 在ListView...

    C#程序开发范例宝典(第2版).part08

    实例066 利用选择控件实现权限设置 83 实例067 利用选择控件实现复杂查询 85 2.6 ListView控件应用 87 实例068 ListView控件间的数据移动 87 实例069 将数据库数据添加到ListView控件 90 实例070 在ListView...

    C#程序开发范例宝典(第2版).part02

    实例066 利用选择控件实现权限设置 83 实例067 利用选择控件实现复杂查询 85 2.6 ListView控件应用 87 实例068 ListView控件间的数据移动 87 实例069 将数据库数据添加到ListView控件 90 实例070 在ListView...

Global site tag (gtag.js) - Google Analytics