(一)数据库建库部分(引用 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整个框架及应用不了解所造成的。