夜雨竹林
落叶无声

(一)数据库建库部分(引用 http://www.cnblogs.com/Leejor/archive/2010/03/21/1690944.html)

目前各类的权限设计已经困扰了我们好久,对于MVC,下面我将通过ActionFilter来扩展我们的权限认证,以下示例是从我的一个课程中心项目中提取出来,希望对各位初学者起到抛砖引玉的作用。

下面首先来设计我们的权限控制的数据库层。

 

下面我来依次介绍每个字段的说明

RoleGroup 权限组表  该表主要对系统权限进行分组,我们的用户可以直接赋予该分组,拥有所有该组权限

RoleID 权限组ID 例:01

RoleName 权限组名称  例:系统管理员

RoleState 组状态 (是否启用) 例:True

 

RoleGroupAppList 表 组权限对应表 该表主要复制每个权限组对应的权限详细列表

ID ID主键 例:1

RoleID 对应权限组的ID 例:01

SysAppID 对应的详细权限组ID 例:01

StartTime 该权限使用的起始日期 例:2009-01-01 ,该字段属性默认为:all ,即不限制起始日期

EndTime 该权限使用的结束日期 例:2009-01-01 ,该字段属性默认为:all ,即不限制结束日期

 

SysAppCate 表 该表主要是对SysAppList详细权限表做分类,当我们的权限页面特别多的时候,该表主要为了方便管理。可省略

SysAppCateID 大类ID 例:01

SysAppCateName 大类名称 例:新闻管理

SysAppCateEName 大类英文名

 

SysAppList 权限详细表 该表主要负责所有权限的基础列表

SysAppID 权限ID 例:01

SysAppCateID 权限大类ID 例:01

SysAppName 权限名称 例:新闻添加

SysAppEName 权限英文名称

SysAppController 权限对应的Controller 例:News

SysAppAction 权限对应的Action 例:Add 本权限设计针对对Action权限限制

IsView 是否为可见 例:True 该字段的设计主要是便于后台的管理和设置,因为有部分Action是没有View层的,比如Post,但是在后台权限管理中我们又要用到他。

 

好了,建库部分就到这里了,下一篇我会介绍逻辑部分,其中会涉及到Repository模式,缓存,自定义的AuthorizeAttribute。

 

 (二)逻辑部分实现

 

在我的项目中,我还是使用的LINQ TO SQL ,因为我的项目不会涉及太多很太复杂的数据库操作业务。当然如果设计,我相信LINQ TO SQL的自定义扩展也能满足需求。

使用Repository模式是最近MVC很多项目采用的解决方案,能把原来我们杂乱的LINQ TO SQL统一封装起来。让我们的架构更清晰。

现在来看看具体实现。

IRepository接口:

interface IRepository<TEntity> where TEntity : class
    {
        IQueryable
<TEntity> FindAll(Expression<Func<TEntity, bool>> exp);
        TEntity Find(Expression
<Func<TEntity, bool>> exp);
        
void Add(TEntity entity);
        
void Delete(TEntity entity);
        
void Save();
    }

 

Repository实现:

 public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        
protected DAL.CourseCenterData db;
        
public Repository()
        {
            db 
= new DAL.CourseCenterData();            
        }


        
/// <summary>
        
/// 查找所有数据
        
/// </summary>
        
/// <returns></returns>
        public IQueryable<TEntity> FindAll()
        {
            
return db.GetTable<TEntity>().Where(p => 1==1);
        }

        
/// <summary>
        
/// 查找所有数据
        
/// </summary>
        
/// <param name="exp">条件表达式</param>
        
/// <returns></returns>
        public IQueryable<TEntity> FindAll(Expression<Func<TEntity, bool>> exp)
        {
            
return db.GetTable<TEntity>().Where(exp);
        }

        
/// <summary>
        
/// 查找一个数据
        
/// </summary>
        
/// <param name="exp">条件表达式</param>
        
/// <returns></returns>
        public TEntity Find(Expression<Func<TEntity, bool>> exp)
        {
            
return db.GetTable<TEntity>().FirstOrDefault(exp);
        }

        
/// <summary>
        
/// 添加数据
        
/// </summary>
        
/// <param name="entity">实体</param>
        public void Add(TEntity entity)
        {
            db.GetTable
<TEntity>().InsertOnSubmit(entity);
        }

        
/// <summary>
        
/// 删除数据
        
/// </summary>
        
/// <param name="entity">实体</param>
        public void Delete(TEntity entity)
        {
            db.GetTable
<TEntity>().DeleteOnSubmit(entity);
        }

        
/// <summary>
        
/// 批量添加数据
        
/// </summary>
        
/// <param name="entity">实体列表</param>
        public void AddAll(IEnumerable<TEntity> entity)
        {
            db.GetTable
<TEntity>().InsertAllOnSubmit(entity);
        }

        
/// <summary>
        
/// 批量删除数据
        
/// </summary>
        
/// <param name="entity">实体列表</param>
        public void DeleteAll(IEnumerable<TEntity> entity)
        {
            db.GetTable
<TEntity>().DeleteAllOnSubmit(entity);
        }

        
/// <summary>
        
/// 对数据做插入,更新,删除操作
        
/// </summary>
        public void Save()
        {
            db.SubmitChanges();
        }

 

网上已经有很多Repository的例子,但是请注意Find中的条件必须是Expression Tree的扩展,否则在数据查询的SQL语句中,你会发现捕获到的将是select一个表之后再来做数据的查询,这在我们海量数据查询时不允许的。

 

现在我们已经有了自己的Repository,下面来建立一个数据库视图把我们上一讲需要的数据拿出来。

 

新建视图ViewRoleGroup,选定一下表和字段

 

 

 

我们通过VS2008新建一个LINQ TO SQL类,拖入这个视图。

然后我们需要思考,这个权限分组视图是系统频繁读取的,需要为他创建一个缓存。

新建一个缓存类Caches:

 

/// <summary>
    
/// 缓存操作基类
    
/// </summary>
    public class Caches
    {
        
/// <summary>
        
/// 建立缓存
        
/// </summary>
        public static object TryAddCache(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, 
            TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemovedCallback)
        {
            
if (HttpRuntime.Cache[key] == null && value != null)
                
return HttpRuntime.Cache.Add(key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemovedCallback);
            
else
                
return null;
        }

        
/// <summary>
        
/// 移除缓存
        
/// </summary>
        public static object TryRemoveCache(string key)
        {
            
if (HttpRuntime.Cache[key] != null)
                
return HttpRuntime.Cache.Remove(key);
            
else
                
return null;
        }

        
/// <summary>
        
/// 移除键中带某关键字的缓存
        
/// </summary>
        public static void RemoveMultiCache(string keyInclude)
        {
            IDictionaryEnumerator CacheEnum 
= HttpRuntime.Cache.GetEnumerator();
            
while (CacheEnum.MoveNext())
            {
                
if (CacheEnum.Key.ToString().IndexOf(keyInclude.ToString()) >= 0)
                    HttpRuntime.Cache.Remove(CacheEnum.Key.ToString());
            }
        }

        
/// <summary>
        
/// 移除所有缓存
        
/// </summary>
        public static void RemoveAllCache()
        {
            IDictionaryEnumerator CacheEnum 
= HttpRuntime.Cache.GetEnumerator();
            
while (CacheEnum.MoveNext())
            {
                HttpRuntime.Cache.Remove(CacheEnum.Key.ToString());
            }
        }
   }

 

 

来看看我们的ViewRoleGroupRepository怎么写。

新建类:ViewRoleGroupRepository

 

public class ViewRoleGroupRepository:Repository<Models.Database.ViewRoleGroup>
    {

        
/// <summary>
        
/// 获取权限视图缓存列表,Key:ViewGroupList
        
/// </summary>
        
/// <returns></returns>
        public List<Models.Database.ViewRoleGroup> GetCacheAll()
        {
            List
<Models.Database.ViewRoleGroup> viewRoleGroup;
            
string key = "ViewGroupList";

            
if (HttpRuntime.Cache[key] != null)
                viewRoleGroup 
= (List<Models.Database.ViewRoleGroup>)HttpRuntime.Cache[key];
            
else
            {

                viewRoleGroup 
= FindAll().ToList();
                Caches.TryAddCache(key, viewRoleGroup, 
null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(20), System.Web.Caching.CacheItemPriority.Normal, null);
            }

            
return viewRoleGroup;
        }
    }

 

 

接下来我们将自定义自己的AuthorizeAttribute,

新建类CenterAuthorizeAttribute

 

 public class CenterAuthorizeAttribute : AuthorizeAttribute
    {

        
public override void OnAuthorization(AuthorizationContext filterContext)
        {

            
string action = (string)filterContext.RouteData.Values["Action"];
            
string controller = (string)filterContext.RouteData.Values["Controller"];
            
string fullName = filterContext.HttpContext.User.Identity.Name;

            
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
                filterContext.HttpContext.Response.Redirect(
string.Format("~/Account/LogOn?from={0}", filterContext.HttpContext.Request.Url.PathAndQuery));
            
else
            {
                var r 
= new BLL.RoleGroupRepository().GetUserRoleGroup(fullName);
                var q 
= new BLL.ViewRoleGroupRepository().GetCacheAll().FindAll(c =>
                    c.RoleID 
== r.RoleID && c.SysAppController == controller && c.SysAppAction == action &&
                    (c.StartTime.Equals(
"all", StringComparison.CurrentCultureIgnoreCase) ? true : (DateTime.Parse(c.StartTime) <= DateTime.Now)) &&
                    (c.EndTime.Equals(
"all", StringComparison.CurrentCultureIgnoreCase) ? true : (DateTime.Parse(c.EndTime) >= DateTime.Now)));
                
if (q.Count == 0)
                    
throw new Exception(string.Format("【用户:{0}】对不起,您没有访问该页面的权限。", fullName));
                
else
                {
                    
string from = filterContext.HttpContext.Request.Params["from"];
                    filterContext.HttpContext.Response.Redirect((
string.IsNullOrEmpty(from) ? "~/Home/Index" : from));
                }
            }
        }

 

请注意var r = new BLL.RoleGroupRepository().GetUserRoleGroup(fullName);这个是我项目中通过用户名获得用户权限分组的实现,大家参考自己项目修改。

大功告成,那怎么用呢,很简单。创建了需要的权限组,例如

超级管理员  然后把对应的Controller和Action权限加入到组后,在需要的Action上进行标注,例如

 

[CenterAuthorize]
        
public ActionResult Index()
        {
            ViewData[
"Message"= "Welcome to ASP.NET MVC!";
            
return View();
        }

 

 

是不是很简单就通过数据库控制到每个需要权限控制的Action咯?当然很多朋友可能还很迷糊,第一次写这类文章。部分代码也没调试。因为从项目剥离出来,所以大家见谅。

UI部分和数据库的操作这里想滤过啦。这些比较简单的东西,大家应该很容易就能实现咯。

 (三)后台UI设计

 

一些朋友提出让我加上细节部分,也有朋友PM说很多地方看不懂不知道该怎么用,由于目前正在赶学校的课程中心,暂没有时间来详细介绍使用,这里我把后台的一些设计截图出来,大家参考设计

另外在第二篇提到的CenterAuthorizeAttribute部分,已经做部分修改,原来的会出现逻辑判断的BUG。

权限大类管理  SysAppCate

 

可以看出,权限大类其实对应的是上部的大类菜单,当添加新的大类,该菜单将加载相应的项并呈现出来。

 

权限列表管理 SysAppList

 

这个界面对应系统所有的权限,有朋友提出如果出现相同的Action,那么将无法分类管理,其实我认为不然,相同的Action一般用到的设计无非是Post和Get的请求变更,那么如果一个页面只可以访问却无法操作,这样还有必要呈现给用户么?大家请注意观察,在这里的设置中如果为是否现实为:False,那么该权限将不会按照分组呈现在左侧的管理菜单中。

 

权限组管理RoleGroup

 

 

 

这个表建立我们需要的系统权限组,这样更方便我们的管理。按照我的项目经验,一个项目很少会出现上百个权限组的要求,在课程中心这个项目中,当然也不会涉及到那么多的权限分类。

这里为每一个权限多了一个“设置”的选项,下面来看看“设置”是怎么完成的.

 

 

这里我做了一个Tab,按照权限的大类分别放置所属的权限并进行加载。开始时间和结束时间是控制该权限在权限组中可以操作的时间,那么设置为Anytime,也就表示开始或者结束为无限期。我在该项目中间会用到申报等一些流程,那么我们必须控制时间来按照需要进行申报。

这里我们还要考虑,应该是当权限发生的更改,才提交这个数据,否则每次都提交这么多权限进行更新,效率上讲我们犯了一个低级的错误。上图的逻辑最终将更新至RoleGroupAppList表中。

 

好了,现在权限整体的结构都已经设计好,接下来将看看怎么把权限应用在对应用户上,这个很简单,但是还是比较说明一下

 

 

 

我们现在只需要把对应的权限组分配给用户,就可以获得该组分配的权限。

 

大家还可以看到我最上面的几个截图中有“帮助” ,这并不是一个固定的页面,我通过

<%= Html.ActionLink("帮助", "Help", new { c = this.ViewContext.RouteData.Values["controller"], a = this.ViewContext.RouteData.Values["action"] ,controller="System" })%>

这样的形式来创建连接,那么可以把它放在MasterPage里,不同的页面中连接其实会变成对应的controller和action,这样建立对应的表,可以很简单的对应这个View视图来加载帮助信息。

最近手头的工作实在太忙,由于时间比较紧,就不一一介绍了。

 

其实MVC给我们更大的灵活度和对视图的控制。我从MVC1 RC版就开始使用MVC做一些小项目,很早抛弃了我认为相对臃肿的WEBFORM,有朋友也曾质疑MVC的编程模式接近ASP,其实这无疑是对于MVC整个框架及应用不了解所造成的。

 

posted on 2010-03-22 23:11  夜雨竹林  阅读(2274)  评论(0编辑  收藏  举报