在WebForm下我们一般会设计个PageBase继承Page,在OnInit方法中实现对基本权限的验证业务,然后所有的页面在继承PageBase直接继承这项基本权验证业务。而在.NET MVC下我们如何再实现这个业务呢? 其实无非也是要设计一个ExtController基类来实现这个业务,而这个ExtController基类的权限验证业务切入点选在哪里合适呢? 这个答案还要从前面的 了解.net MVC的实现原理Controller/Action 章节寻找。(标签属性IActionFilter, IAuthorizationFilter暂且不涉及)

一、寻找合适时机的切入点

简单的回顾一下这个过程,首先Controller中的Action要被执行,那Controller就要被实例化,接着才能根据请求的URL,调用对应的Action。Controller是被MvcHandler的ProcessRequest或BeginProcessRequest方法的这段代码创建的。

  private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
// If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks
// at Request.Form) to work correctly without triggering full validation.
bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current);
if (isRequestValidationEnabled == true) {
ValidationUtility.EnableDynamicValidation(HttpContext.Current);
} AddVersionHeader(httpContext);
RemoveOptionalRoutingParameters(); // Get the controller type
string controllerName = RequestContext.RouteData.GetRequiredString("controller"); // Instantiate the controller and call Execute
factory = ControllerBuilder.GetControllerFactory();
controller = factory.CreateController(RequestContext, controllerName);
if (controller == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.ControllerBuilder_FactoryReturnedNull,
factory.GetType(),
controllerName));
}
}

相信很多人都通IOC容器重新实现过ControllerBuilder的DefaultControllerFactory

            ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(ContainerFactory.GetContainer()));

通过上面代码我们可以知道Controller是在何时被创建的,我们第一次有机会拦截Controller对其做一定处理的时机,就是重新实现ControllerBuilder的DefaultControllerFactory中的这个方法GetControllerInstance。我们再继续寻找其它的切入点,当MvcHandler通过ControllerFactory创建了请求对应的Controller的实例后,会继续调用自己的这个方法

执行IController接口的Execute方法,我们来看一下最终是执行的Controller.Execute->ControllerBase.Execute的方法

  protected virtual void Execute(RequestContext requestContext) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null) {
throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
} VerifyExecuteCalledOnce();
Initialize(requestContext); using (ScopeStorage.CreateTransientScope()) {
ExecuteCore();
}
}

注意ControllerBase中Execute本身这个方法,以及其中的Initialize方法,ExecuteCore方法均是 Action被执行前的实现基本权限验证业务的切入点。

另外在Controller中ExecuteCore 方法被重载实现

其中ActionInvoker.InvokeAction方法是反射执行对应URL请求的Action,在这个方法,我们可以清楚看到当在Action被执行前,又分别先执行了Controller中的这个两个方法

 protected override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
} protected override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}

因此Controller的OnActionExecuting,OnAuthorization也可以做为Action 被执行前的权限验证切入点。好了,通过源码找到了这么多的切入点,那我们就分别来实现一下试试。

二、权限验证切入点的尝试

首先设计个继承Controller的基类BaseController,详细设计请参照注释。大部分思考内容都写在代码注释部分,文章中就不重复写了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace Demo.Mvc.Core
{
///<summary>
/// 登录成功的时候被放入SESSION中
///</summary>
public class UserState
{
public string PassCode { set; get; }
public string UserId { set; get; }
public string UserName { set; get; } //public List<Role> RoleCollection { set; get; } //角色集
//public List<Auth> AuthCollection { set; get; } //权限集
} public class BaseController:System.Web.Mvc.Controller
{
///<summary>
/// 用户信息
///</summary>
public UserState UserState { set; get; } ///<summary>
/// 微软设计这个无参的构造的Controller 有利于使用IOC容器提高对象的创建效率
/// 如果设计了System.Web.Routing.RequestContext参数,由于每次来的RequestContext都不相同
/// 则Controller 就要不停的动态创建
///</summary>
public BaseController()
{
//无参的构造
} ///<summary>
/// 改造一个构造函数切入点
/// 这种方式虽然使得切入机会早,并且可以较早的构造中对业务层注入一些用户信息。
/// 但是缺点就是每次都要动态反射(因为每次来的HttpContext请求都不相同)
///</summary>
///<param name="requestContext"></param>
public BaseController(System.Web.Routing.RequestContext requestContext)
{
this.OnInit(requestContext); //这样可以在构造的时候就切入了
} ///<summary>
/// 比较早的切入点 在ControllerFactory被创建的时候顺便就实现权限验证
///</summary>
///<param name="requestContext"></param>
public virtual void OnInit(System.Web.Routing.RequestContext requestContext)
{
//这里实现用户信息的相关验证业务
if (requestContext.HttpContext.Session["UserState"] != null)
{
UserState userState = requestContext.HttpContext.Session["UserState"] as UserState;
string passCode = requestContext.HttpContext.Request.Cookies["UserState"].Value.Trim(); string controllerName = requestContext.RouteData.Values["controller"].ToString()+"Controller";
string actionName = requestContext.RouteData.Values["action"].ToString(); //判断有没有Action操作权限
//userState.AuthCollection.Contains(controllerName + "/" + acitonName);
}
else
{
//非登录用户跳转
//requestContext.HttpContext.Response.Redirect("/html/complex.html");
}
} ///<summary>
/// 比较晚的切入点 IController在执行Execute之后,Action被执行之前使用的
///</summary>
public virtual void OnInit()
{
//这里实现用户信息的相关验证业务
if (this.HttpContext.Session["UserState"] != null)
{
UserState userState = this.HttpContext.Session["UserState"] as UserState;
string passCode = this.HttpContext.Request.Cookies["UserState"].Value.Trim(); string controllerName = this.RouteData.Values["controller"].ToString() + "Controller";
string actionName = this.RouteData.Values["action"].ToString(); //实现Action操作权限验证业务
//userState.AuthCollection.Contains(controllerName + "/" + acitonName);
}
else
{
//非登录用户跳转
//requestContext.HttpContext.Response.Redirect("/html/complex.html");
}
} protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
base.Execute(requestContext);
//this.OnInit();//---------------------------------------------切入点
} protected override void ExecuteCore()
{
base.ExecuteCore();
//this.OnInit();//---------------------------------------------切入点
} protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
this.OnInit(); //---------------------------------------------切入点
} //除上述的方式以下方式
//我们还可以使用IActionFilter, IAuthorizationFilter标签属性的方式实现权限验证 (这个不在本次讨研究范围内)
protected override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
//this.OnInit();//---------------------------------------------切入点
} protected override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//this.OnInit();//---------------------------------------------切入点
} }
}

里面列出了实现基类的中的多个切入点,其中第一个OnInit 方法的设计 是由于Controller在构造实例时 并没有合适的切入的点,所以通过RequestContext的注入可以使我们可以将切入点提到ControllerFactory中。 第二个OnInit的切入比较晚,都是在IController的 Execute被执行后,对应的Action被执行前。当然第二种情况已经完全满足了我们的业务需要,为什么还要第一种OnInit的设计?我们先来重构一下ControllerFactory,改变默认的Controller的创建方式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.Practices.Unity; namespace Demo.Mvc
{
public class UnityControllerFactory : DefaultControllerFactory
{
private readonly IUnityContainer container; public UnityControllerFactory(IUnityContainer container)
{
//要做异常处理
this.container = container;
} protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType.IsSubclassOf(typeof(Core.BaseController)))
{
//早期切入点 方式1,使用默认的无构造方式
//目的必免直接反射Controller,提高效率
Core.BaseController controller = container.Resolve(controllerType) as Core.BaseController;
controller.OnInit(requestContext); //切入点 //假如这个位置使用构造注入那OnInit就会构造函数执行时就实现相关验证业务包括UserState已经创建
//这样做导致Controller每次都要被动态反射创建,但好处是可以使用权限切入点更早
//这样权限业务可以更早的注入业务层中(如UserState传递至业务层)
//代码省略 return controller;
}
else
return container.Resolve(controllerType) as IController;
} public override IController CreateController(RequestContext requestContext, string controllerName)
{
return base.CreateController(requestContext, controllerName);
} } }

设计Controller构造切入点,主要是因为子类MenuController的构造一般会注入如Public MenuController(FileService service)构造,如果我们可以BaseController构造时就完成权限对象的创建,那就完全有机会在子类构造将权限对象传递到Service中,甚至利用IOC容器一样可以实现。 一般大家都会在MenuController中构造一个全局的FileService对象,如果基类还未获得权限对象,那只能等到对应的Action要被执行前时这些权限验证对象才会被创建,也就是说FileService获得验证模型时间就被推迟到Action中,亦或者MenuController重载BaseController的一系列切入点。这个不是Filter机制能带来的效果。

下面是实现的MenuController ,详细参照注释

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel;
using Demo.Model;
using Demo.Service.IService;
using Demo.Mvc.ViewObject; namespace Demo.Mvc.Controller
{ [HandleError]
public class MenuController:Core.BaseController
{
private IMenuService service; //如果我们ControllerFactory中使用构造生成Controller,那OnInit在构造的同时也成功获得了UserState
//并且可以在这个时候将UserState注入到业务层中
public MenuController(IMenuService service)
{ this.service = service;
//this.service.UserState = this.UserState;
//这样做的优点就是不用再重载基类的Initialize方法,前提ControllerFactory中使用构造生成Controller,这样可以过早的拿到Session.
} //由于在ControllerFactory无构造生成Controller,就会导OnInit的切入时机比较晚,这个时候UserState还是NULL(即还没有拿到HttpContext中的Session)
//所以我们只能通过在重载一次基类的方式,才能获得已经创建的UserState信息
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
//this.service.UserState = this.UserState;
}

        public MenuController()
{ } public ActionResult ShowContent(object model)
{
//service.Update(model,this.UserState) //用户信息传至业务层 通过业务层方法参数注入
return View();
} public ActionResult Index(UserVO userVo)
{
if (ModelState.IsValid)
{
ViewData["Key1"] = "TEST1";
TempData["Key2"] = "TEST2";
}
else
{ }
return View(); //默认封装ViewResult返回
} public ActionResult DownLoadFile(string fileName)
{
return File(Server.MapPath(@"/Images/view.jpg"), @"image/gif");
} public ActionResult ToOther(string fileName)
{
return Redirect(@"http://localhost:1847/Menu/ShowContent");
} } }

以上就是对.NET MVC 基本权限验证实现的分析和模拟实现。如果.net mvc 的controller不用来实现业务,还要另外提取实现业务层的情况下,那本身对Controller的Action拦截是否还有意义呢? 因为对具体业务方法拦截才是最终目的,这导致类似AOP的拦截框架是与业务层绑定的,因此只要针对业务层实现即可了,并且还可以重用。而此时.net mvc 在提供Controller对Action 的拦截机制似乎就变得多余了。我没必要对Action拦截一次,还要对业务层具体业务再拦截一次。大家对这个问题怎么看?希望分享一下自己的经验。

.NET MVC权限设计思考之切入点的更多相关文章

  1. java权限设计思考

    1.粗粒度权限设计与细粒度权限设计             粗粒度(Coarse-graind)        表示类别级,即仅考虑对象的类别(the   type   of   object),不考 ...

  2. mvc 权限设计

    1.http://blog.csdn.net/vera514514/article/details/8285154 2.http://www.cnblogs.com/cmsdn/p/3433995.h ...

  3. ASP.NET MVC +EasyUI 权限设计(二)环境搭建

    请注明转载地址:http://www.cnblogs.com/arhat 今天突然发现博客园出问题了,老魏使用了PC,手机,平板都访问博客园了,都是不能正常的访问,原因是不能加载CSS,也就是不能访问 ...

  4. ASP.NET MVC +EasyUI 权限设计(一)开篇

    在前一段时间中,老魏的确非常的忙碌,Blog基本上没有更新了,非常的抱歉,那么在后面的时间中,老魏会尽量的抽时间来写的,可能时间上就不太富裕了.今天开始呢,老魏会和大家分享一下关于权限设计的有关文章, ...

  5. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(13)-权限设计

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(13)-权限设计 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架搭建    (2):数据 ...

  6. AppBox升级进行时 - 扁平化的权限设计

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. AppBox v2.0中的权限实现 AppBox v2.0中权限管理中涉及三个 ...

  7. 一个基于RBAC0的通用权限设计清单

    注:RBAC0与RBAC1不同在于权限继承.关于RBAC1的权限设计,敬请关注作者后续CSDN博客.1,用户表 保存系统用户信息,如张三.李四,字段可以有id.name.fullname.email. ...

  8. MVC+Bootstrap设计

    MVC+Bootstrap) 二 框架设计 文章目录: 一.前言 二.结构图 三.项目搭建 四.代码生成 五.实现接口 六.依赖倒置 七.登录实现 八.最后 一.前言 这个框架是从最近几年做过的项目中 ...

  9. 权限设计实现(MVC4+Bootstrap+ PetaPoco+Spring.Net)

    权限设计实现(MVC4+Bootstrap+ PetaPoco+Spring.Net) 一.前言 至毕业后一直在做企业Web开发,做过的项目也有不少,每个项目的框架设计都不是一样,但是每个项目的权限模 ...

随机推荐

  1. SQL Server将数据导出到SQL脚本文件

    http://www.studyofnet.com/news/list-8883.2-1-4.html 一.SQL Server 2008将数据导出到SQL脚本文件 1.打开SQL Server200 ...

  2. 【原创】IBM Websphere 报错:JSPG0120E: 为 pageEncoding 属性和匹配 URI 模式的配置元素指定不同的值是非法的。

    websphere中间件,在打开一个jsp页面时报: IBM Websphere 报错:JSPG0120E: 为 pageEncoding 属性和匹配 URI 模式的配置元素指定不同的值是非法的. . ...

  3. easyui的datetimebox时间格式化详解

    今天公司让用easyui的datetimebox组件,而且还要让格式化成大家通用的那种,网上搜了很多,但差不多都是复制黏贴的,最后请教了下螃蟹. 感谢螃蟹抽空给做了个例子,现在拿出来和大家分享下,效果 ...

  4. Python——Day2(基础知识练习一)

    1.执行Python脚本的两种方式1)调用解释器 Python +绝对路径+文件名称2)调用解释器 Python +相对路径+文件名称 2.简述位.字节的关系8位为1个字节 3.简述ASCII.uni ...

  5. Map之HashMap的get与put流程,及hash冲突解决方式

    在java中HashMap作为一种Map的实现,在程序中我们经常会用到,在此记录下其中get与put的执行过程,以及其hash冲突的解决方式: HashMap在存储数据的时候是key-value的键值 ...

  6. 网站出现502 bad getway

    最近项目之余,领导叫解决下系统网站经常出现502的问题,作为小头头的我,怎能不顶上. 流程开始走起,先查nginx,嗯,配置是大众的.是不是缓存溢出了呢.调节buffer的值 .貌似也没什么影响啊.5 ...

  7. Zend 官方框架增加 Swoole 协程支持 !

    前言 Zend Framework 是 PHP 的官方框架,随着 Zend-Expressive-Swoole 0.2.2 的发布,率先支持了 Swoole 4 的协程功能,现在可以仅通过一个配置即可 ...

  8. 使用LeNet训练自己的手写图片数据

    一.前言 本文主要尝试将自己的数据集制作成lmdb格式,送进lenet作训练和测试,参考了http://blog.csdn.net/liuweizj12/article/details/5214974 ...

  9. 1、认识和安装MongoDB

    MongoDB简介:MongoDB是一个基于分布式文件存储的数据库,由C++语言编写.目的是为WEB应用提供扩展的高性能的数据存储解决方案.MongoDB是一个介于关系型数据库和非关系型数据库之间的产 ...

  10. redis 参数配置总结

    redis.conf 配置项说明如下 1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程   daemonize no 2. 当Redis以守护进程方式运行时, ...